Some more prefiltering
Pre-filtering genes
It is customary to remove genes that have very low expression, since their signal-to-noise ratio is very small to yield any biologically significant results.
Keep in the dds object only genes with at least 10 reads in more than 25% of the samples
Useful functions:
#> [1] 20180
nSamples <- ncol(dds)*.25
genes.keep <- rowSums(counts(dds) >=10) > nSamples
dds <- dds[genes.keep,]
nrow(dds)
#> [1] 17399
We went from about 20,200 genes down to about 17,400.
Sample characterization
Now we want to explore the samples in our dataset. In order to compare the columns of our matrix, however, we need to normalize the counts. The simplest way would be to take the sum of each column (i.e., the total library size of each sample) and divide the counts by the value. The DESeq2 package offers some more sophisticated methods, and we will use the “variance stabilising transformation” in this tutorial (the vst() function). The output is a DESeqTransform object, and the values calculated can be extracted from it using the assay() function:
norm_expr <- vst(dds) %>% assay
Execute the command above to obtain a matrix of normalized gene expression
norm_expr <- vst(dds) %>% assay
Before we plot the expression values we just calculated, let’s install and load the pheatmap package, used to plot heatmaps.
Now, instead of plotting the whole normalized expression matrix that we obtained using the vst() function, let us only plot the top 1,000 most varying genes using the pheatmap() function.
Calculate the standard deviation for each gene and pick the top 1,000 genes with the highest. Plot the vst expression matrix for those genes using the pheatmap() function.
Useful functions:
apply()
sd()
rowSds()
arrange()
desc()
slice_max()
pheatmap()
plot.genes <- tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
slice_max(sd, n=1000) %>%
pull(gene_name)
norm_expr[plot.genes, ] %>% pheatmap()

The heatmap is not that informative to compare across samples, because it only shows that some genes have a higher level of expression than others. By default, pheatmap() does not scale the data, but we can do that using scale=“row” or scale=“column”.
Plot the heatmap again but this time using scale=“row”
norm_expr[plot.genes, ] %>%
pheatmap(scale="row")

What could the two clusters be representing? We can annotate the columns of the heatmap by using the option “annotation_col” of pheatmap(). We need to provide a data.frame with the variables to annotate. For example:
annotation_col=as.data.frame(colData(dds)[,c("condition","origin")])
Plot the same heatmap but adding annotations for the condition, the origin, the RIN values and the post-mortem time
norm_expr[plot.genes, ] %>%
pheatmap(scale="row", annotation_col=as.data.frame(colData(dds)[,c("condition","origin", "rin", "pm_time_min")]))

The fact that the two cohorts are so different is a reason for concern. We know that the cohorts have very different RIN values and post-mortem intervals, and gene expression may reflect those differences.
Pairwise correlation between samples
We are going to calculate how different each pair of samples are, and then summarize each sample by its median difference to the rest of the samples.
The cor() function calculates the correlation between each pair of columns of the input matrix, and we can take advantage of it.
Use the cor() function on the subset of 1,000 most varying genes from the vst transformed gene expression data to calculate all pairwise correlations and plot the resulting correlation matrix using pheatmap().
Useful functions:
Cors <- cor(norm_expr[plot.genes, ])
pheatmap(Cors)

Calculate the median correlation per sample.
Useful functions:
medCors <- rowMedians(Cors)
names(medCors) <- rownames(Cors)
medCors %>% sort
#> SL283585 SL283597 SL283590 SL283575 SL283616 SL283618 SL283570 SL283581
#> 0.7567623 0.7572315 0.7606560 0.7871291 0.8066868 0.8169079 0.8392480 0.8453424
#> SL283566 SL283584 SL283576 SL283610 SL283599 SL283595 SL283596 SL283614
#> 0.8495857 0.8548754 0.8627927 0.8654529 0.8686231 0.8698628 0.8741854 0.8835448
#> SL283587 SL283583 SL283617 SL283589 SL283592 SL283565 SL283605 SL283608
#> 0.8841093 0.8846946 0.8890795 0.8904316 0.8909524 0.8911854 0.8920549 0.8924803
#> SL283586 SL283598 SL283600 SL283604 SL283603 SL283568 SL283593 SL283569
#> 0.8941451 0.8952702 0.8963120 0.8989816 0.9008564 0.9013622 0.9015567 0.9016811
#> SL283601 SL283612 SL283572 SL283574 SL283606 SL283602 SL283578 SL283580
#> 0.9024367 0.9046887 0.9079857 0.9086827 0.9087302 0.9100107 0.9100342 0.9122101
#> SL283591 SL283588 SL283579 SL283567 SL283577 SL283573 SL283594 SL283582
#> 0.9133564 0.9152114 0.9152738 0.9155428 0.9178124 0.9234158 0.9240418 0.9274764
#> SL283571
#> 0.9275740
A straightforward way to display outliers in this case is to plot a boxplot with ggplot2, which by default plots outliers as points:
ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))
Plot a ggplot2 boxplot using the command above.
ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))

We can see that four samples (SL283585, SL283597, SL283590, SL283575) are shown as outliers - they are quite different from all the rest.
Now we will plot all the samples in the first 2 principal components of a PCA. The easiest is probably to use the plotPCA() function from the DESeq2 package, which works on the result from vst(). Note that you have to run vst() again on the dds object, since plotPCA() works on its output.
Useful functions:
Use the plotPCA() function to visualize the samples in the first 2 principal components of the expression data.

Do the same, but for each cohort (origin) separately.
plotPCA(vst(dds)[,colData(dds)$origin=="PW"])

plotPCA(vst(dds)[,colData(dds)$origin=="NBB"])

Can you spot the two clusters in the NBB cohort?
The plotPCA() function from the DESeq2 package does something sneaky behind the scenes. If you check the help for the function, you’ll see that the argument “ntop” is set to 500 by default. To save time, the function carries out the PCA only on the top 500 most varying genes, similarly to what we were doing before (but using the top 1,000). What happens if we use, for example, only the top 100 instead?
Use plotPCA() with ntop=400, then ntop=200, and finally with ntop=10 for all samples.
plotPCA(vst(dds), ntop=400)

plotPCA(vst(dds), ntop=200)

plotPCA(vst(dds), ntop=10)

The fewer genes we use to construct the PCA, the more obvious the clusters become. What is going on? What could it be that separates our samples in two groups? Let’s have a look at this top 10 most varying genes.
First, load the gene descriptions that we used in the first tutorial using
geneNames <- readRDS(url("https://git.app.uib.no/neuromics/cell-composition-rna-pd/-/raw/master/Data/EnsDb.Hsapiens.v75.Rds"))
Load the geneNames.Rds file into the R session and convert it to a tibble
geneNames <- readRDS(url("https://git.app.uib.no/neuromics/cell-composition-rna-pd/-/raw/master/Data/EnsDb.Hsapiens.v75.Rds")) %>%
as_tibble
Then, find the gene names of the top10 most varying genes in the geneNames object.
Find the top10 most varying genes in the geneNames object.
tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
slice_max(sd, n=10) %>%
left_join(geneNames) %>%
select(gene_name, seqnames) %>%
unique
In which chromosome are most of these genes located? Can it explain the groups you obseved in the PCA?
Plot the PCA again using the plotPCA() function using ntop=10 and intgroup=“sex”.
plotPCA(vst(dds), ntop=10, intgroup="sex")

Although sex-associated gene expression is not exclusively restricted to sex chromosomes, it is of a much greater magnitude in the sex chromosomes.
Check sex assignment
We can use the strong association between sex-chromosome genes and sex to assess potential mis-labeling of samples. A quick way is to use all genes located in the Y chromosome to run a PCA.
Plot all the samples in the two principal components of the PCA of the Y-located genes, colouring the points by the sex of the sample.
NOTE: use the vst() function on the whole dataset, subset after.
Ygenes <- geneNames %>% filter(seqnames == "Y", gene_biotype == "protein_coding") %>%
pull(gene_name) %>% unique
vst(dds)[rownames(dds) %in% Ygenes,] %>% plotPCA(ntop=Inf, intgroup="sex")

As you can see, most of the variance is explained by the first principal component, as expected. It would in fact be enough to plot the values for the PC1.
correct_sign <- function(x) {
if (median(x)<0) return(-1*x)
else return(x)
}
pca1 <- vst(dds)[rownames(dds) %in% Ygenes,] %>%
assay %>%
t %>%
prcomp
pca1$x %>%
as_tibble(rownames="sample_id") %>%
select(sample_id, PC1) %>%
mutate(PC1=correct_sign(PC1)) %>%
left_join(as_tibble(colData(dds))) %>%
ggplot(aes(x=sex, y=PC1)) +
geom_boxplot(outlier.shape=NA) +
geom_jitter(aes(colour=sex), height=0, width=.2)
correct_sign <- function(x) {
if (median(x)<0) return(-1*x)
else return(x)
}
pca1 <- vst(dds)[rownames(dds) %in% Ygenes,] %>%
assay %>%
t %>%
prcomp
pca1$x %>%
as_tibble(rownames="sample_id") %>%
select(sample_id, PC1) %>%
mutate(PC1=correct_sign(PC1)) %>%
left_join(as_tibble(colData(dds))) %>%
ggplot(aes(x=sex, y=PC1)) +
geom_boxplot(outlier.shape=NA) +
geom_jitter(aes(colour=sex), height=0, width=.2)

For simplicity, we are going to remove from the dds object all genes mapped to the sex chromosomes.
Remove all X and Y genes from the dds object.
sex.genes <- geneNames %>% filter(seqnames %in% c("X","Y")) %>%
pull(gene_name) %>% unique
dim(dds)
#> [1] 17399 49
dds <- dds[!rownames(dds) %in% sex.genes,]
dim(dds)
#> [1] 16755 49
Recalculate the pairwise correlations between samples as before with the filtered dataset. Plot the values and plot the PCA with ntop=1000
norm_expr <- vst(dds) %>% assay
top1000.genes <- tibble(gene_name=rownames(norm_expr), sd=norm_expr %>% rowSds ) %>%
slice_max(sd, n=1000) %>%
pull(gene_name)
Cors <- cor(norm_expr[top1000.genes, ])
medCors <- rowMedians(Cors)
names(medCors) <- rownames(Cors)
medCors %>% sort
#> SL283597 SL283585 SL283590 SL283575 SL283616 SL283618 SL283581 SL283570
#> 0.7627503 0.7682360 0.7782123 0.7977671 0.8229228 0.8239435 0.8447489 0.8559964
#> SL283566 SL283584 SL283576 SL283599 SL283610 SL283596 SL283595 SL283583
#> 0.8576488 0.8708081 0.8778189 0.8798999 0.8831353 0.8856723 0.8859760 0.8899975
#> SL283614 SL283592 SL283587 SL283617 SL283565 SL283603 SL283608 SL283589
#> 0.8914506 0.8977170 0.8985736 0.9024982 0.9032124 0.9049152 0.9060812 0.9098299
#> SL283605 SL283568 SL283604 SL283593 SL283586 SL283598 SL283600 SL283612
#> 0.9100418 0.9114346 0.9129014 0.9129787 0.9129787 0.9136635 0.9140187 0.9146281
#> SL283602 SL283578 SL283601 SL283574 SL283606 SL283572 SL283569 SL283591
#> 0.9175766 0.9194735 0.9209084 0.9209463 0.9216333 0.9224393 0.9234645 0.9243526
#> SL283580 SL283567 SL283579 SL283588 SL283594 SL283577 SL283573 SL283582
#> 0.9244825 0.9249829 0.9256202 0.9308943 0.9318754 0.9344447 0.9352384 0.9364574
#> SL283571
#> 0.9429330
ggplot(tibble(medCors)) + geom_boxplot(aes(x=1, y=medCors))


In order to plot the sample ids, we need to use the “returnData=TRUE” option, in the plotPCA() function which, instead of plotting, will then returns the data in a format that can be easily used with ggplot2, adding a layer with the labels:
ggplot(data) +
geom_text(aes(label=name), nudge_y=1.5, size=3)
We could also, of course, run the PCA ourselves using prcomp().
plotPCA(vst(dds), returnData=TRUE) %>%
ggplot(aes(x=PC1, y=PC2)) +
geom_point(aes(colour=condition)) +
geom_text(aes(label=name), nudge_y=1.5, size=3)

Evaluating the PCA plot and the heatmap of correlations, do you think it is justified to remove any of the samples?
Cell type estimation
Before proceeding with the differential expression analysis, since we are dealing with human brain tissue, we need to investigate what the cell composition is in our samples. This is of fundamental importance in these type of datasets because most of the variability in gene expression in explained by the cell types.
We will use a very simple approach to estimate cell types similar, in a way, to what we used to “estimate” sex: summarize gene expression of a set of selected markers as their first principal component (PC1). The marker genes will be known cell type-specific genes.
Let’s load a list of markers of cortical cell types from Neuroespresso:
cortical.markers <- read_tsv("./Data/cortical_markers.tsv")
Read the tab-separated file “cortical_markers.tsv” into a tibble
cortical.markers <- read_tsv("./Data/cortical_markers.tsv")
Now let us run a PCA (as we have done until now) but subsetting the genes to the neuronal markers. We will use the prcomp() function and extract the values of the PC1 for each sample.
Run a PCA using prcomp() only on neuronal markers. Explore the result and find where the coordinates for the first PC are.
estimate_ct <- function(object, genes, ct="PC1", rescale=TRUE) {
object.vst <- vst(object)
pca <- object.vst[rownames(object.vst) %in% genes,] %>%
assay() %>%
t() %>%
prcomp()
result <- as_tibble(pca$x, rownames="sample_id") %>%
select(sample_id, PC1) %>%
mutate(PC1=correct_sign(PC1))
colnames(result) <- c("sample_id", ct)
if (rescale) result[,ct] <- scale(result[,ct])[,1]
return(result)
}
neuronal.est <- estimate_ct(dds, cortical.markers %>% filter(cell_type=="Neuron") %>% pull(gene_name), ct="Neuron")
Now we can do that for every other cortical cell type.
Calculate the first principal component for the other cell types and add them all to the metadatata of the dds object (colData(dds)).
ct.est <- lapply(unique(cortical.markers$cell_type), function(ct){
message(paste0(ct, "..."))
Genes <- filter(cortical.markers, cell_type==ct) %>% pull(gene_name)
estimate_ct(dds, genes=Genes, ct=ct)
}) %>% Reduce("left_join", .)
newColData <- left_join(as_tibble(colData(dds)), ct.est, by="sample_id") %>%
as("DataFrame")
rownames(newColData) <- newColData$sample_id
all(rownames(colData(dds)) == rownames(newColData))
#> [1] TRUE
colData(dds) <- newColData
And finally, do the same with the RNA markers for the cortical synapsome, which are not quite the same as the set of neuronal markers. These are RNAs found in the synapses, obtained from Hafner et al. 2019.
synaptosome.markers <- read_tsv("./Data/synaptosome_markers.tsv")
#syn <- qusage::read.gmt("./Data/synaptosome_symbols.gmt") %>% qdapTools::list2df() %>% as_tibble
#colnames(syn) <- c("gene_id", "cell_type")
#write_tsv(syn, "./Data/synaptosome_markers.tsv")
synaptosome.markers <- read_tsv("./Data/synaptosome_markers.tsv")
syn.est <- estimate_ct(dds, genes=unique(synaptosome.markers$gene_id), ct="synaptosome")
all(syn.est$sample_id == colData(dds)$sample_id)
#> [1] TRUE
colData(dds)$Synapses <- syn.est$synaptosome
colData(dds)
#> DataFrame with 49 rows and 16 columns
#> sample_id origin age_years sex pm_time_min condition
#> <character> <character> <numeric> <character> <numeric> <factor>
#> SL283595 SL283595 PW 79 M 2880 Control
#> SL283593 SL283593 PW 85 F 4320 Control
#> SL283618 SL283618 PW 88 F 3000 Control
#> SL283601 SL283601 PW 65 M 2880 Control
#> SL283565 SL283565 NBB 85 F 425 Control
#> ... ... ... ... ... ... ...
#> SL283592 SL283592 PW 69 M 3420 Case
#> SL283588 SL283588 PW 72 M 2880 Case
#> SL283600 SL283600 PW 78 F 1440 Case
#> SL283594 SL283594 PW 82 M 5040 Case
#> SL283590 SL283590 PW 95 M 2880 Case
#> rin Batch sample_id_paper Astrocyte Endothelial Microglia
#> <numeric> <character> <character> <numeric> <numeric> <numeric>
#> SL283595 3.4 2 Ctr-1 -1.184423 -1.160479 0.955535
#> SL283593 5.9 2 Ctr-10 -0.309941 0.537182 0.469155
#> SL283618 5.6 4 Ctr-11 0.217133 -1.489182 -2.429756
#> SL283601 4.9 3 Ctr-14 -0.498226 0.213325 0.744386
#> SL283565 6.6 3 Ctr-23 0.153747 0.319363 -0.260804
#> ... ... ... ... ... ... ...
#> SL283592 4.5 2 PD-5 -0.8294955 -0.938985 0.201632
#> SL283588 5.6 4 PD-6 -0.0199344 0.681234 0.483412
#> SL283600 6.2 3 PD-7 -1.0950377 -0.521545 1.167569
#> SL283594 6.1 1 PD-8 0.6990216 0.729143 0.121045
#> SL283590 5.9 2 PD-9 -2.7878270 -2.481439 1.771681
#> Oligo OligoPrecursors Neuron Synapses
#> <numeric> <numeric> <numeric> <numeric>
#> SL283595 0.782419 -0.6619144 -0.705792 -0.902350
#> SL283593 0.532277 0.0857936 0.630432 0.461680
#> SL283618 -0.160397 -1.1894146 -0.866282 -1.078199
#> SL283601 0.244944 -0.3442277 -0.264953 -0.349270
#> SL283565 -2.747786 -0.1931905 0.123844 0.269135
#> ... ... ... ... ...
#> SL283592 -1.5972658 -1.140781 -0.4162497 -0.652125
#> SL283588 0.0917733 -0.213461 0.0183912 -0.155062
#> SL283600 -1.1012333 -0.734881 -0.6989150 -0.817925
#> SL283594 -0.2983306 0.625531 0.8981723 0.721039
#> SL283590 -0.1372109 -1.721758 -2.5800732 -2.100692
How can we assess which variables are responsible for most of the variation in gene expression? PCA is very helpful in this regards. By definition, the first principal component will maximize the variance that it explains by combining linearly the variables. In a way, it is “summarizing” gene expression by optimally projecting it into one dimension. Then, with the leftover variance not explained by PC1, it will do the same, but with the constaint that PC2 needs to be perpendicular to PC1.
The first principal components are often referred to as the main lines of variation, because they gather as much as possible, and often serve as a good “summary” of the multidimensional data.
If we want to assess whether cell type composition (or other variables) explain most of the variation, a simple way could be to test for association between variables and PCs. This is a bit involved programmatically, so here is the code:
scale.vec <- function(x) scale(x)[,1]
pca2 <- dds %>%
vst %>%
assay %>%
t %>%
prcomp
Metadata.pca <- pca2$x %>%
as_tibble(rownames="sample_id") %>%
select(sample_id, PC1:PC5) %>%
mutate_if(is.numeric, scale.vec) %>%
left_join(as_tibble(colData(dds)))
Metadata.pca <- Metadata.pca %>% select(-sample_id, -origin, -sample_id_paper) %>% mutate_if(is.character, as.factor) %>%
mutate_if(is.factor, as.integer)
allCors <- lapply(paste0("PC", 1:5), function(pc) {
lapply(colnames(Metadata.pca)[6:ncol(Metadata.pca)], function(var){
#message(paste0(pc, " vs ", var))
lm(formula=as.formula(paste0(pc, "~", var)), data=Metadata.pca) %>%
broom::tidy() %>% filter(term!="(Intercept)") %>%
mutate(PC=pc, VAR=var)
}) %>% Reduce("bind_rows",.)
}) %>% Reduce("bind_rows",.)
Pvals <- allCors %>% select(p.value, PC, VAR) %>%
pivot_wider(names_from=VAR, values_from=p.value)
p.mat <- as.matrix(Pvals[,-1])
rownames(p.mat) <- Pvals$PC
Estimates <- allCors %>% select(estimate, PC, VAR) %>%
pivot_wider(names_from=VAR, values_from=estimate)
e.mat <- as.matrix(Estimates[,-1])
rownames(e.mat) <- Estimates$PC
corrplot::corrplot(e.mat, p.mat=p.mat)
scale.vec <- function(x) scale(x)[,1]
pca2 <- dds %>%
vst %>%
assay %>%
t %>%
prcomp
Metadata.pca <- pca2$x %>%
as_tibble(rownames="sample_id") %>%
select(sample_id, PC1:PC5) %>%
mutate_if(is.numeric, scale.vec) %>%
left_join(as_tibble(colData(dds)))
Metadata.pca <- Metadata.pca %>% select(-sample_id, -origin, -sample_id_paper) %>% mutate_if(is.character, as.factor) %>%
mutate_if(is.factor, as.integer)
allCors <- lapply(paste0("PC", 1:5), function(pc) {
lapply(colnames(Metadata.pca)[6:ncol(Metadata.pca)], function(var){
#message(paste0(pc, " vs ", var))
lm(formula=as.formula(paste0(pc, "~", var)), data=Metadata.pca) %>%
broom::tidy() %>% filter(term!="(Intercept)") %>%
mutate(PC=pc, VAR=var)
}) %>% Reduce("bind_rows",.)
}) %>% Reduce("bind_rows",.)
Pvals <- allCors %>% select(p.value, PC, VAR) %>%
pivot_wider(names_from=VAR, values_from=p.value)
p.mat <- as.matrix(Pvals[,-1])
rownames(p.mat) <- Pvals$PC
Estimates <- allCors %>% select(estimate, PC, VAR) %>%
pivot_wider(names_from=VAR, values_from=estimate)
e.mat <- as.matrix(Estimates[,-1])
rownames(e.mat) <- Estimates$PC
corrplot::corrplot(e.mat, p.mat=p.mat)

Do you see anything interesting? May it be that the RNA quality (RIN) is associated with cell type composition? That can easily be tested with a quick linear regression: try predicting the RIN values with the estimated values for synaptosomal content, then try the same with the estimated neuronal content, and then with both at the same time in the model. The formulas are as follow:
- rin ~ Synapses
- rin ~ Neuron
- rin ~ Synapses + Neuron
Then compare the resulting models using the summary() function on the linear fits.
Run 3 linear regressions with rin as a response variable and 1. Synapses; 2. Neuron; and 3. Synapses + Neuron as predictors. Check the adjusted R-squared values to compare the models.
# RIN
lm.syn <- lm(rin~Synapses, data=as.data.frame(colData(dds)))
lm.neu <- lm(rin~Neuron, data=as.data.frame(colData(dds)))
lm.both <- lm(rin~Neuron+Synapses, data=as.data.frame(colData(dds)))
lm.both.cohort <- lm(rin~Neuron+Synapses+origin, data=as.data.frame(colData(dds)))
lm.syn %>% summary
#>
#> Call:
#> lm(formula = rin ~ Synapses, data = as.data.frame(colData(dds)))
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -1.5931 -0.7244 0.0062 0.6431 2.4892
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 5.9490 0.1348 44.141 < 2e-16 ***
#> Synapses 1.2082 0.1362 8.873 1.31e-11 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.9434 on 47 degrees of freedom
#> Multiple R-squared: 0.6262, Adjusted R-squared: 0.6182
#> F-statistic: 78.73 on 1 and 47 DF, p-value: 1.31e-11
#>
#> Call:
#> lm(formula = rin ~ Neuron, data = as.data.frame(colData(dds)))
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -1.88488 -0.93143 -0.09251 0.55718 2.74936
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 5.9490 0.1552 38.343 < 2e-16 ***
#> Neuron 1.0846 0.1568 6.919 1.09e-08 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 1.086 on 47 degrees of freedom
#> Multiple R-squared: 0.5046, Adjusted R-squared: 0.494
#> F-statistic: 47.87 on 1 and 47 DF, p-value: 1.085e-08
#>
#> Call:
#> lm(formula = rin ~ Neuron + Synapses, data = as.data.frame(colData(dds)))
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -1.76501 -0.49485 -0.05657 0.66050 1.64005
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 5.9490 0.1230 48.378 < 2e-16 ***
#> Neuron -1.7596 0.5442 -3.234 0.00226 **
#> Synapses 2.9213 0.5442 5.369 2.53e-06 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.8608 on 46 degrees of freedom
#> Multiple R-squared: 0.6954, Adjusted R-squared: 0.6822
#> F-statistic: 52.51 on 2 and 46 DF, p-value: 1.334e-12
lm.both.cohort %>% summary
#>
#> Call:
#> lm(formula = rin ~ Neuron + Synapses + origin, data = as.data.frame(colData(dds)))
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -1.68523 -0.54675 -0.01054 0.52954 1.62786
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 6.0734 0.2152 28.222 < 2e-16 ***
#> Neuron -1.5875 0.5989 -2.651 0.011 *
#> Synapses 2.7036 0.6280 4.305 8.9e-05 ***
#> originPW -0.2177 0.3082 -0.706 0.484
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 0.8655 on 45 degrees of freedom
#> Multiple R-squared: 0.6988, Adjusted R-squared: 0.6787
#> F-statistic: 34.79 on 3 and 45 DF, p-value: 8.653e-12
Synaptosome expression seem to explain RIN the best. But what if we consider “origin” as well? Which is the “best” model? Check the adjusted R squared for the models or run anova() on two models to test whether a more complex model is significantly better at capturing RIN variation.
Add “origin” as a covariate to the previous models and compare the adjusted R squared between the simpler and more comples versions of the model. Even better, use the anova() function to compare the models.
anova(
lm(rin~Synapses, data=as.data.frame(colData(dds))),
lm(rin~Synapses+Neuron, data=as.data.frame(colData(dds)))
)
anova(
lm(rin~Synapses, data=as.data.frame(colData(dds))),
lm(rin~Synapses+origin, data=as.data.frame(colData(dds)))
)
anova(
lm(rin~Neuron, data=as.data.frame(colData(dds))),
lm(rin~Neuron+origin, data=as.data.frame(colData(dds)))
)
anova(
lm(rin~Synapses+origin, data=as.data.frame(colData(dds))),
lm(rin~Synapses+Neuron+origin, data=as.data.frame(colData(dds)))
)
lm.final <- lm(rin~Synapses+Neuron, data=as.data.frame(colData(dds)))
You can also check the diagnostic plots by simply using the function plot() on the linear fit. Do you see a sample that you may want to exclude before running your model again? Does it make a difference?




Differential gene expression
Running an differential expression analysis
With the DESeq2 package, we can use the function DESeq() on our dds object to run the whole differential expression pipeline, which will run three steps in order:
estimateSizeFactors(dds): estimation of sample-specific normalization parameters
estimateDispersions(dds): estimation of gene-specific dispersion parameters
nbinomWaldTest(dds): negative binomial generalized linear model to calculate the desired log2-fold changes and calculation of Wald statistics
When you run DESeq, by default it will calculate the fit according to whatever model you had specified when you created the object. The model’s formula can be printed on screen using design(dds).
Let’s change the model’s formula of the object like so:
design(dds) <- ~ sex + age_years + condition
and then run DESeq() on the dds object.
Note 1: if you have manually changed something in the colData(dds), it may be that some columns are of type “character”. They should be converted to factors.
Note 2: to avoid some headaches later on, we want to be very explicit with our variable of interest (the disease status) by specifying which of the two levels (“case” or “control”) is the REFERENCE value. This can be done by using the relevel() function like so:
colData(dds)$condition <- relevel(colData(dds)$condition, ref="Control")
Run a differential expression analysis on the dds object using the DESeq() function. The model’s formula should be ~ sex + age + condition. Overwrite with the returned object the dds object.
Useful functions:
mutate_if()
is.character()
DESeq()
# convert to char to factor
chr_cols <- sapply(colData(dds), is.character)
colData(dds)[chr_cols] <- lapply(colData(dds)[chr_cols], as.factor)
# relevel condition just in case
colData(dds)$condition <- relevel(colData(dds)$condition, ref="Control")
design(dds) <- ~ sex + age_years + condition
dds <- DESeq(dds)
Looking at the results
The results() function extracts the log2 fold changes and p values from the dds object (as long as the DESeq function was run). By default, the function will extract the log2 fold change for the last variable in the design formula (in our case, the disease status). However, it is always good practice to explicitly specify the contrast when calling results() with the “contrast” or “name” argument. There are different ways of specifying contrasts but, for example, if we wanted to extract the differential expression results between males and females, we could use:
results(dds, name="sex_M_vs_F")
or
results(dds, contrast=c("sex", "M", "F"))
they are equivalent. A nice trick to see which contrasts can be extracted using “name=”, you can use
resultsNames(dds)
which prints all possible values for the contrast name. In our case, the possible values for the “name=” argument are: “Intercept”, “sex_M_vs_F”, “age_years”, and “condition_Case_vs_Control”
Use the results() function on the dds object to extract the differential expression between cases and controls and find out whether any genes are significant after correction.
#results(dds, name="sex_M_vs_F")
#results(dds, contrast=c("sex", "M", "F"))
res <- results(dds, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res %>% arrange(padj)
Are there any significant changes in gene expression associated with the disease?
We have seen that RIN is associated with the main lines of variation in gene expression. We should expect, then, that RIN is adding a lot of “uninteresting” variation to the dataset, i.e., a lot of noise. What happens if you add “rin” as a covariate?
You’ll need to update the formula and re-run the whole pipeline like so:
design(dds) <- ~ sex + age_years + rin + condition
dds <- DESeq(dds)
res <- results(dds, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res %>% arrange(padj)
Re-run the analyses with RIN as a covariate.
dds_rin <- dds
design(dds_rin) <- ~ sex + age_years + rin + condition
dds_rin <- DESeq(dds_rin)
res_rin <- results(dds_rin, name="condition_Case_vs_Control") %>% as_tibble(rownames="gene_name")
res_rin %>% arrange(padj)
What do you think has changed? How does the extra covariate added influence the results? What do you think would have happened if you added to the model a covariate that was highly associated with the condition of interest (i.e., “confounded”)?
Since the cohorts had different RINs and post-mortem intervals, try to run the cohorts separately and explore the results.
Run the differential expression analyses separately for each cohort.
dds_pw <- dds[,colData(dds)$origin=="PW"]
dds_pw <- DESeq(dds_pw)
results(dds_pw) %>% as_tibble(rownames="gene_name") %>% arrange(padj)
dds_nbb <- dds[,colData(dds)$origin=="NBB"]
dds_nbb <- DESeq(dds_nbb)
results(dds_nbb) %>% as_tibble(rownames="gene_name") %>% arrange(padj)
How can two cohorts exhibit such differences in their results? What does this suggest about inter-study replicability?
Gene set enrichment analyse
We are going to use the fgsea package to run a gene set enrichment analysis on your results.
Install the fgsea package using Bioconductor::install(fgsea) and load the package.
We can download “gmt” formatted files from MSigDB website, which can be imported into R using the gmtPathways() function from the fgsea package. I have already downloaded the KEGG database, which can be loaded into a variable like so:
KEGG_pathways <- gmtPathways("c2.cp.kegg.v2022.1.Hs.symbols.gmt")
Load the KEGG pathway list using the previous command.
KEGG_pathways <- gmtPathways("c2.cp.kegg.v2022.1.Hs.symbols.gmt")
We are going to prepare an input for the fgsea() function. We need to rank the genes according to our value of interest (in our case, it will be the “stat” column of our differential expression results, although you could use -log10(p-value) or log2FoldChange).
Create a vector with the “stat” values from the results and name the values with the corresponding gene names.
ranks <- res$stat
names(ranks) <- res$gene_name
Run gsea_res <- fgsea(KEGG_pathways, ranks) and explore the results (“ranks” is the vector of differential expression stats).
gsea_res <- fgsea(KEGG_pathways, ranks) %>% arrange(padj)
gsea_res
What is at the top? Can you run the GSEA with the results from the two alternate models (i.e., with and withouth accounting for the effect of RIN)?
Create a new vector with the stats of the appropriate results and run fgsea again.
ranks_rin <- res_rin$stat
names(ranks_rin) <- res_rin$gene_name
gsea_res_rin <- fgsea(KEGG_pathways, ranks_rin) %>% arrange(padj)
gsea_res_rin
What happens if you add Neuron or Synapses to the design model? How that does affect the GSEA results?
Run another differential expression analysis using Neuron and/or Synapses as covariates and explore the differences in the enriched pathways.
dds_neu <- dds
design(dds_neu) <- ~ sex + age_years + rin + Neuron + Synapses + condition
dds_neu <- DESeq(dds_neu)
res_neu <- results(dds_neu) %>% as_tibble(rownames="gene_name")
ranks_neu <- res_neu$stat
names(ranks_neu) <- res_neu$gene_name
gsea_res_neu <- fgsea(KEGG_pathways, ranks_neu) %>% arrange(padj)
gsea_res_neu
Can you think of a way to identify the pathways that are more susceptible to RIN and/or Neuron correction?
LS0tDQp0aXRsZTogIlBhcnQgMjogUk5BLXNlcSBhbmFseXNpcyINCmF1dGhvcjogIkdvbnphbG8gUy4gTmlkbyINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgICBodG1sX25vdGVib29rOg0KICAgICAgICB0b2M6IHRydWUNCiAgICAgICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgICAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KPCEtLQ0KVG8gY29udmVydCBNYXJrZG93biBkb2N1bWVudCB0byBIVE1MLA0KUiAtZSAicm1hcmtkb3duOjpyZW5kZXIoJzIuUm1kJykiDQoNClRvIHNwZWxsLWNoZWNrLCBzcGVsbGluZzo6c3BlbGxfY2hlY2tfZmlsZXMoKQ0KLS0+DQoNCmBgYHtjc3MsIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFfQ0KcSB7IGNvbG9yOiBCbGFjazsgZm9udC13ZWlnaHQ6IGJvbGQ7IGJhY2tncm91bmQtY29sb3I6IGtoYWtpOyB9DQpgYGANCg0KDQpgYGB7ciBnbG9iYWxfb3B0aW9ucywgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9DQpyZXF1aXJlKCJrbml0ciIpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLnBhdGg9J0ZpZ3NfUm1kLycsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTMsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBlY2hvPVRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgI2VjaG89RkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgZXZhbD1UUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICNldmFsPUZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHM9VFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAjcmVzdWx0cz1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZT1GQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50PSIjPiIpDQpyZXF1aXJlKCJrYWJsZUV4dHJhIikNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KIyBQYWNrYWdlcw0KDQpBcyB1c3VhbCwgd2Ugd2lsbCBuZWVkIHRvIGxvYWQgc29tZSBwYWNrYWdlcy4NCg0KDQo8cT5Mb2FkIHRoZSBgdGlkeXZlcnNlYCBhbmQgYERFU2VxMmAgcGFja2FnZXMsIGluc3RhbGwgdGhlbSBpZiB0aGV5IGFyZSBub3QNCmFscmVhZHkuPC9xPg0KDQoNCmBgYHtyfQ0KcmVxdWlyZSgidGlkeXZlcnNlIikNCnJlcXVpcmUoIkRFU2VxMiIpDQpgYGANCg0KTm93LCBsb2FkIHRoZSBERVNlcTIgb2JqZWN0IHRoYXQgeW91IHNhdmVkIHByZXZpb3VzbHkgdXNpbmcgdGhlIGByZWFkUkRTYA0KZnVuY3Rpb24uDQoNCjxxPkxvYWQgdGhlIFJkcyBvYmplY3QgY29udGFpbmluZyB0aGUgREVTZXEyIG9iamVjdCBpbnRvIHRoZSAiZGRzIiB2YXJpYWJsZTwvcT4NCg0KYGBge3J9DQpkZHMgPC0gcmVhZFJEUygiLi9kZHMuUmRzIikNCmBgYA0KDQpXaGlsZSB3ZSBhbHJlYWR5IGFwcGxpZWQgc29tZSBmaWx0ZXJpbmcgdGhlIHRvIGNvdW50IG1hdHJpeCB0aGF0IHdlIHVzZWQgYXMgYW4NCmlucHV0LCB3ZSBjYW4gc3RpbGwgcHJlLWZpbHRlciB0aGUgZGF0YXNldCBmdXJ0aGVyLiBZb3UgY2FuLCBpbiBmYWN0LCB0cmVhdCB0aGUNCkRFU2VxRGF0YVNldCBvYmplY3QgKGluIG91ciBgZGRzYCB2YXJpYWJsZSkgYXMgYSBtYXRyaXggdG8gc3Vic2V0IGl0Lg0KDQpGb3IgZXhhbXBsZSwgYGRpbShkZHMpYCB3aWxsIHJldHVybiB0aGUgZGltZW5zaW9ucyBvZiB0aGUgbWF0cml4IChudW1iZXIgb2YNCmZlYXR1cmVzIG9yIGdlbmVzLCBhbmQgbnVtYmVyIG9mIHNhbXBsZXMpLiBTaW1pbGFybHksIHdlIGNhbiBzdWJzZXQgdG8gdGhlDQpmaXJzdCAzIHNhbXBsZXMgYW5kIGZpcnN0IDEwIGdlbmVzIHdpdGggYGRkc1sxOjEwLDE6M11gLg0KDQpUbyBhY2Nlc3MgdGhlIGZ1bGwgY291bnQgbWF0cml4LCB5b3UgY2FuIHVzZSBgY291bnRzKGRkcylgLCBhbmQgdG8gYWNjZXNzIHRoZQ0Kc2FtcGxlIG1ldGFkYXRhLCB5b3UgdXNlIGBjb2xEYXRhKGRkcylgLg0KDQojIFNvbWUgbW9yZSBwcmVmaWx0ZXJpbmcNCg0KIyMgUHJlLWZpbHRlcmluZyBnZW5lcw0KDQpJdCBpcyBjdXN0b21hcnkgdG8gcmVtb3ZlIGdlbmVzIHRoYXQgaGF2ZSB2ZXJ5IGxvdyBleHByZXNzaW9uLCBzaW5jZSB0aGVpcg0Kc2lnbmFsLXRvLW5vaXNlIHJhdGlvIGlzIHZlcnkgc21hbGwgdG8geWllbGQgYW55IGJpb2xvZ2ljYWxseSBzaWduaWZpY2FudA0KcmVzdWx0cy4NCg0KPHE+S2VlcCBpbiB0aGUgYGRkc2Agb2JqZWN0IG9ubHkgZ2VuZXMgd2l0aCBhdCBsZWFzdCAxMCByZWFkcyBpbiBtb3JlIHRoYW4gMjUlDQpvZiB0aGUgc2FtcGxlczwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgcm93U3VtcygpYA0KDQpgYGB7cn0NCm5yb3coZGRzKQ0KblNhbXBsZXMgPC0gbmNvbChkZHMpKi4yNQ0KZ2VuZXMua2VlcCA8LSByb3dTdW1zKGNvdW50cyhkZHMpID49MTApID4gblNhbXBsZXMNCmRkcyA8LSBkZHNbZ2VuZXMua2VlcCxdDQpucm93KGRkcykNCmBgYA0KDQpXZSB3ZW50IGZyb20gYWJvdXQgMjAsMjAwIGdlbmVzIGRvd24gdG8gYWJvdXQgMTcsNDAwLg0KDQoNCiMjIFNhbXBsZSBjaGFyYWN0ZXJpemF0aW9uDQoNCk5vdyB3ZSB3YW50IHRvIGV4cGxvcmUgdGhlIHNhbXBsZXMgaW4gb3VyIGRhdGFzZXQuIEluIG9yZGVyIHRvIGNvbXBhcmUgdGhlDQpjb2x1bW5zIG9mIG91ciBtYXRyaXgsIGhvd2V2ZXIsIHdlIG5lZWQgdG8gbm9ybWFsaXplIHRoZSBjb3VudHMuIFRoZSBzaW1wbGVzdA0Kd2F5IHdvdWxkIGJlIHRvIHRha2UgdGhlIHN1bSBvZiBlYWNoIGNvbHVtbiAoaS5lLiwgdGhlIHRvdGFsIGxpYnJhcnkgc2l6ZSBvZg0KZWFjaCBzYW1wbGUpIGFuZCBkaXZpZGUgdGhlIGNvdW50cyBieSB0aGUgdmFsdWUuIFRoZSBgREVTZXEyYCBwYWNrYWdlIG9mZmVycw0Kc29tZSBtb3JlIHNvcGhpc3RpY2F0ZWQgbWV0aG9kcywgYW5kIHdlIHdpbGwgdXNlIHRoZSAidmFyaWFuY2Ugc3RhYmlsaXNpbmcNCnRyYW5zZm9ybWF0aW9uIiBpbiB0aGlzIHR1dG9yaWFsICh0aGUgYHZzdCgpYCBmdW5jdGlvbikuICBUaGUgb3V0cHV0IGlzIGENCmBERVNlcVRyYW5zZm9ybWAgb2JqZWN0LCBhbmQgdGhlIHZhbHVlcyBjYWxjdWxhdGVkIGNhbiBiZSBleHRyYWN0ZWQgZnJvbSBpdA0KdXNpbmcgdGhlIGBhc3NheSgpYCBmdW5jdGlvbjoNCg0KICAgIG5vcm1fZXhwciA8LSB2c3QoZGRzKSAlPiUgYXNzYXkNCg0KPHE+RXhlY3V0ZSB0aGUgY29tbWFuZCBhYm92ZSB0byBvYnRhaW4gYSBtYXRyaXggb2Ygbm9ybWFsaXplZCBnZW5lDQpleHByZXNzaW9uPC9xPg0KDQpgYGB7cn0NCm5vcm1fZXhwciA8LSB2c3QoZGRzKSAlPiUgYXNzYXkNCmBgYA0KDQpCZWZvcmUgd2UgcGxvdCB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgd2UganVzdCBjYWxjdWxhdGVkLCBsZXQncyBpbnN0YWxsIGFuZCBsb2FkDQp0aGUgYHBoZWF0bWFwYCBwYWNrYWdlLCB1c2VkIHRvIHBsb3QgaGVhdG1hcHMuDQoNCmBgYHtyfQ0KcmVxdWlyZSgicGhlYXRtYXAiKQ0KYGBgDQoNCk5vdywgaW5zdGVhZCBvZiBwbG90dGluZyB0aGUgd2hvbGUgbm9ybWFsaXplZCBleHByZXNzaW9uIG1hdHJpeCB0aGF0IHdlDQpvYnRhaW5lZCB1c2luZyB0aGUgYHZzdCgpYCBmdW5jdGlvbiwgbGV0IHVzIG9ubHkgcGxvdCB0aGUgdG9wIDEsMDAwIG1vc3QNCnZhcnlpbmcgZ2VuZXMgdXNpbmcgdGhlIGBwaGVhdG1hcCgpYCBmdW5jdGlvbi4NCg0KPHE+Q2FsY3VsYXRlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gZm9yIGVhY2ggZ2VuZSBhbmQgcGljayB0aGUgdG9wIDEsMDAwIGdlbmVzDQp3aXRoIHRoZSBoaWdoZXN0LiBQbG90IHRoZSB2c3QgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRob3NlIGdlbmVzIHVzaW5nIHRoZQ0KYHBoZWF0bWFwKClgIGZ1bmN0aW9uLjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgYXBwbHkoKWANCiogYHNkKClgDQoqIGByb3dTZHMoKWANCiogYGFycmFuZ2UoKWANCiogYGRlc2MoKWANCiogYHNsaWNlX21heCgpYA0KKiBgcGhlYXRtYXAoKWANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMH0NCnBsb3QuZ2VuZXMgPC0gdGliYmxlKGdlbmVfbmFtZT1yb3duYW1lcyhub3JtX2V4cHIpLCBzZD1ub3JtX2V4cHIgJT4lIHJvd1NkcyApICU+JQ0KICAgIHNsaWNlX21heChzZCwgbj0xMDAwKSAlPiUNCiAgICBwdWxsKGdlbmVfbmFtZSkNCm5vcm1fZXhwcltwbG90LmdlbmVzLCBdICU+JSBwaGVhdG1hcCgpDQpgYGANCg0KVGhlIGhlYXRtYXAgaXMgbm90IHRoYXQgaW5mb3JtYXRpdmUgdG8gY29tcGFyZSBhY3Jvc3Mgc2FtcGxlcywgYmVjYXVzZSBpdCBvbmx5DQpzaG93cyB0aGF0IHNvbWUgZ2VuZXMgaGF2ZSBhIGhpZ2hlciBsZXZlbCBvZiBleHByZXNzaW9uIHRoYW4gb3RoZXJzLiBCeQ0KZGVmYXVsdCwgYHBoZWF0bWFwKClgIGRvZXMgbm90IHNjYWxlIHRoZSBkYXRhLCBidXQgd2UgY2FuIGRvIHRoYXQgdXNpbmcNCnNjYWxlPSJyb3ciIG9yIHNjYWxlPSJjb2x1bW4iLg0KDQo8cT5QbG90IHRoZSBoZWF0bWFwIGFnYWluIGJ1dCB0aGlzIHRpbWUgdXNpbmcgc2NhbGU9InJvdyI8L3E+DQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTB9DQpub3JtX2V4cHJbcGxvdC5nZW5lcywgXSAlPiUNCiAgICBwaGVhdG1hcChzY2FsZT0icm93IikNCmBgYA0KDQpXaGF0IGNvdWxkIHRoZSB0d28gY2x1c3RlcnMgYmUgcmVwcmVzZW50aW5nPyBXZSBjYW4gYW5ub3RhdGUgdGhlIGNvbHVtbnMgb2YgdGhlDQpoZWF0bWFwIGJ5IHVzaW5nIHRoZSBvcHRpb24gImFubm90YXRpb25fY29sIiBvZiBgcGhlYXRtYXAoKWAuIFdlIG5lZWQgdG8NCnByb3ZpZGUgYSBgZGF0YS5mcmFtZWAgd2l0aCB0aGUgdmFyaWFibGVzIHRvIGFubm90YXRlLiBGb3IgZXhhbXBsZToNCg0KICAgIGFubm90YXRpb25fY29sPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpWyxjKCJjb25kaXRpb24iLCJvcmlnaW4iKV0pDQoNCjxxPiBQbG90IHRoZSBzYW1lIGhlYXRtYXAgYnV0IGFkZGluZyBhbm5vdGF0aW9ucyBmb3IgdGhlIGNvbmRpdGlvbiwgdGhlIG9yaWdpbiwNCnRoZSBSSU4gdmFsdWVzIGFuZCB0aGUgcG9zdC1tb3J0ZW0gdGltZTwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCm5vcm1fZXhwcltwbG90LmdlbmVzLCBdICU+JQ0KICAgIHBoZWF0bWFwKHNjYWxlPSJyb3ciLCBhbm5vdGF0aW9uX2NvbD1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKVssYygiY29uZGl0aW9uIiwib3JpZ2luIiwgInJpbiIsICJwbV90aW1lX21pbiIpXSkpDQpgYGANCg0KVGhlIGZhY3QgdGhhdCB0aGUgdHdvIGNvaG9ydHMgYXJlIHNvIGRpZmZlcmVudCBpcyBhIHJlYXNvbiBmb3IgY29uY2Vybi4gV2Uga25vdw0KdGhhdCAgdGhlIGNvaG9ydHMgaGF2ZSB2ZXJ5IGRpZmZlcmVudCBSSU4gdmFsdWVzIGFuZCBwb3N0LW1vcnRlbSBpbnRlcnZhbHMsIGFuZA0KZ2VuZSBleHByZXNzaW9uIG1heSByZWZsZWN0IHRob3NlIGRpZmZlcmVuY2VzLg0KDQoNCiMjIyBQYWlyd2lzZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHNhbXBsZXMNCg0KV2UgYXJlIGdvaW5nIHRvIGNhbGN1bGF0ZSBob3cgZGlmZmVyZW50ICplYWNoIHBhaXIqIG9mIHNhbXBsZXMgYXJlLCBhbmQgdGhlbg0Kc3VtbWFyaXplIGVhY2ggc2FtcGxlIGJ5IGl0cyBtZWRpYW4gZGlmZmVyZW5jZSB0byB0aGUgcmVzdCBvZiB0aGUgc2FtcGxlcy4NCg0KVGhlIGBjb3IoKWAgZnVuY3Rpb24gY2FsY3VsYXRlcyB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBlYWNoIHBhaXIgb2YgY29sdW1ucyBvZg0KdGhlIGlucHV0IG1hdHJpeCwgYW5kIHdlIGNhbiB0YWtlIGFkdmFudGFnZSBvZiBpdC4NCg0KPHE+VXNlIHRoZSBgY29yKClgIGZ1bmN0aW9uIG9uIHRoZSBzdWJzZXQgb2YgMSwwMDAgbW9zdCB2YXJ5aW5nIGdlbmVzIGZyb20gdGhlDQp2c3QgdHJhbnNmb3JtZWQgZ2VuZSBleHByZXNzaW9uIGRhdGEgdG8gY2FsY3VsYXRlIGFsbCBwYWlyd2lzZSBjb3JyZWxhdGlvbnMgYW5kDQpwbG90IHRoZSByZXN1bHRpbmcgY29ycmVsYXRpb24gbWF0cml4IHVzaW5nIGBwaGVhdG1hcCgpYC48L3E+DQoNClVzZWZ1bCBmdW5jdGlvbnM6DQoNCiogYGNvcigpYA0KKiBgcGhlYXRtYXAoKWANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCkNvcnMgPC0gY29yKG5vcm1fZXhwcltwbG90LmdlbmVzLCBdKQ0KcGhlYXRtYXAoQ29ycykNCmBgYA0KDQo8cT5DYWxjdWxhdGUgdGhlIG1lZGlhbiBjb3JyZWxhdGlvbiBwZXIgc2FtcGxlLjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgcm93TWVkaWFucygpYA0KKiBgc29ydCgpYA0KDQoNCmBgYHtyfQ0KbWVkQ29ycyA8LSByb3dNZWRpYW5zKENvcnMpDQpuYW1lcyhtZWRDb3JzKSA8LSByb3duYW1lcyhDb3JzKQ0KbWVkQ29ycyAlPiUgc29ydA0KYGBgDQoNCkEgc3RyYWlnaHRmb3J3YXJkIHdheSB0byBkaXNwbGF5IG91dGxpZXJzIGluIHRoaXMgY2FzZSBpcyB0byBwbG90IGEgYm94cGxvdCB3aXRoDQpnZ3Bsb3QyLCB3aGljaCBieSBkZWZhdWx0IHBsb3RzIG91dGxpZXJzIGFzIHBvaW50czoNCg0KICAgIGdncGxvdCh0aWJibGUobWVkQ29ycykpICsgZ2VvbV9ib3hwbG90KGFlcyh4PTEsIHk9bWVkQ29ycykpDQoNCjxxPlBsb3QgYSBnZ3Bsb3QyIGJveHBsb3QgdXNpbmcgdGhlIGNvbW1hbmQgYWJvdmUuPC9xPg0KDQpgYGB7cn0NCmdncGxvdCh0aWJibGUobWVkQ29ycykpICsgZ2VvbV9ib3hwbG90KGFlcyh4PTEsIHk9bWVkQ29ycykpDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IGZvdXIgc2FtcGxlcyAoU0wyODM1ODUsIFNMMjgzNTk3LCBTTDI4MzU5MCwgU0wyODM1NzUpIGFyZSBzaG93bg0KYXMgb3V0bGllcnMgLSB0aGV5IGFyZSBxdWl0ZSBkaWZmZXJlbnQgZnJvbSBhbGwgdGhlIHJlc3QuDQoNCk5vdyB3ZSB3aWxsIHBsb3QgYWxsIHRoZSBzYW1wbGVzIGluIHRoZSBmaXJzdCAyIHByaW5jaXBhbCBjb21wb25lbnRzIG9mIGEgUENBLg0KVGhlIGVhc2llc3QgaXMgcHJvYmFibHkgdG8gdXNlIHRoZSBgcGxvdFBDQSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgREVTZXEyYA0KcGFja2FnZSwgd2hpY2ggd29ya3Mgb24gdGhlIHJlc3VsdCBmcm9tIGB2c3QoKWAuIE5vdGUgdGhhdCB5b3UgaGF2ZSB0byBydW4NCmB2c3QoKWAgYWdhaW4gb24gdGhlIGBkZHNgIG9iamVjdCwgc2luY2UgYHBsb3RQQ0EoKWAgd29ya3Mgb24gaXRzIG91dHB1dC4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgdnN0KClgDQoqIGBwbG90UENBKClgDQoNCjxxPlVzZSB0aGUgYHBsb3RQQ0EoKWAgZnVuY3Rpb24gdG8gdmlzdWFsaXplIHRoZSBzYW1wbGVzIGluIHRoZSBmaXJzdCAyDQpwcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aGUgZXhwcmVzc2lvbiBkYXRhLjwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTV9DQpwbG90UENBKHZzdChkZHMpKQ0KYGBgDQoNCjxxPkRvIHRoZSBzYW1lLCBidXQgZm9yIGVhY2ggY29ob3J0IChvcmlnaW4pIHNlcGFyYXRlbHkuPC9xPg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NX0NCnBsb3RQQ0EodnN0KGRkcylbLGNvbERhdGEoZGRzKSRvcmlnaW49PSJQVyJdKQ0KcGxvdFBDQSh2c3QoZGRzKVssY29sRGF0YShkZHMpJG9yaWdpbj09Ik5CQiJdKQ0KYGBgDQoNCkNhbiB5b3Ugc3BvdCB0aGUgdHdvIGNsdXN0ZXJzIGluIHRoZSBOQkIgY29ob3J0Pw0KDQpUaGUgYHBsb3RQQ0EoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYERFU2VxMmAgcGFja2FnZSBkb2VzIHNvbWV0aGluZyBzbmVha3kgYmVoaW5kDQp0aGUgc2NlbmVzLiBJZiB5b3UgY2hlY2sgdGhlIGhlbHAgZm9yIHRoZSBmdW5jdGlvbiwgeW91J2xsIHNlZSB0aGF0IHRoZQ0KYXJndW1lbnQgIm50b3AiIGlzIHNldCB0byA1MDAgYnkgZGVmYXVsdC4gVG8gc2F2ZSB0aW1lLCB0aGUgZnVuY3Rpb24gY2Fycmllcw0Kb3V0IHRoZSBQQ0Egb25seSBvbiB0aGUgdG9wIDUwMCBtb3N0IHZhcnlpbmcgZ2VuZXMsIHNpbWlsYXJseSB0byB3aGF0IHdlIHdlcmUNCmRvaW5nIGJlZm9yZSAoYnV0IHVzaW5nIHRoZSB0b3AgMSwwMDApLiBXaGF0IGhhcHBlbnMgaWYgd2UgdXNlLCBmb3IgZXhhbXBsZSwNCm9ubHkgdGhlIHRvcCAxMDAgaW5zdGVhZD8NCg0KPHE+VXNlIGBwbG90UENBKClgIHdpdGggbnRvcD00MDAsIHRoZW4gbnRvcD0yMDAsIGFuZCBmaW5hbGx5IHdpdGggbnRvcD0xMCBmb3INCmFsbCBzYW1wbGVzLjwvcT4NCg0KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTV9DQpwbG90UENBKHZzdChkZHMpLCBudG9wPTQwMCkNCnBsb3RQQ0EodnN0KGRkcyksIG50b3A9MjAwKQ0KcGxvdFBDQSh2c3QoZGRzKSwgbnRvcD0xMCkNCmBgYA0KDQpUaGUgZmV3ZXIgZ2VuZXMgd2UgdXNlIHRvIGNvbnN0cnVjdCB0aGUgUENBLCB0aGUgbW9yZSBvYnZpb3VzIHRoZSBjbHVzdGVycw0KYmVjb21lLiBXaGF0IGlzIGdvaW5nIG9uPyBXaGF0IGNvdWxkIGl0IGJlIHRoYXQgc2VwYXJhdGVzIG91ciBzYW1wbGVzIGluIHR3bw0KZ3JvdXBzPyBMZXQncyBoYXZlIGEgbG9vayBhdCB0aGlzIHRvcCAxMCBtb3N0IHZhcnlpbmcgZ2VuZXMuDQoNCkZpcnN0LCBsb2FkIHRoZSBnZW5lIGRlc2NyaXB0aW9ucyB0aGF0IHdlIHVzZWQgaW4gdGhlIGZpcnN0IHR1dG9yaWFsIHVzaW5nDQoNCiAgICBnZW5lTmFtZXMgPC0gcmVhZFJEUyh1cmwoImh0dHBzOi8vZ2l0LmFwcC51aWIubm8vbmV1cm9taWNzL2NlbGwtY29tcG9zaXRpb24tcm5hLXBkLy0vcmF3L21hc3Rlci9EYXRhL0Vuc0RiLkhzYXBpZW5zLnY3NS5SZHMiKSkNCg0KPHE+TG9hZCB0aGUgZ2VuZU5hbWVzLlJkcyBmaWxlIGludG8gdGhlIFIgc2Vzc2lvbiBhbmQgY29udmVydCBpdCB0byBhDQp0aWJibGU8L3E+DQoNCmBgYHtyfQ0KZ2VuZU5hbWVzIDwtIHJlYWRSRFModXJsKCJodHRwczovL2dpdC5hcHAudWliLm5vL25ldXJvbWljcy9jZWxsLWNvbXBvc2l0aW9uLXJuYS1wZC8tL3Jhdy9tYXN0ZXIvRGF0YS9FbnNEYi5Ic2FwaWVucy52NzUuUmRzIikpICU+JQ0KICAgIGFzX3RpYmJsZQ0KYGBgDQoNClRoZW4sIGZpbmQgdGhlIGdlbmUgbmFtZXMgb2YgdGhlIHRvcDEwIG1vc3QgdmFyeWluZyBnZW5lcyBpbiB0aGUgYGdlbmVOYW1lc2ANCm9iamVjdC4NCg0KPHE+RmluZCB0aGUgdG9wMTAgbW9zdCB2YXJ5aW5nIGdlbmVzIGluIHRoZSBgZ2VuZU5hbWVzYCBvYmplY3QuPC9xPg0KICAgIA0KYGBge3J9DQp0aWJibGUoZ2VuZV9uYW1lPXJvd25hbWVzKG5vcm1fZXhwciksIHNkPW5vcm1fZXhwciAlPiUgcm93U2RzICkgJT4lDQogICAgc2xpY2VfbWF4KHNkLCBuPTEwKSAlPiUNCiAgICBsZWZ0X2pvaW4oZ2VuZU5hbWVzKSAlPiUNCiAgICBzZWxlY3QoZ2VuZV9uYW1lLCBzZXFuYW1lcykgJT4lDQogICAgdW5pcXVlDQpgYGANCg0KSW4gd2hpY2ggY2hyb21vc29tZSBhcmUgbW9zdCBvZiB0aGVzZSBnZW5lcyBsb2NhdGVkPyBDYW4gaXQgZXhwbGFpbiB0aGUgZ3JvdXBzDQp5b3Ugb2JzZXZlZCBpbiB0aGUgUENBPw0KDQo8cT5QbG90IHRoZSBQQ0EgYWdhaW4gdXNpbmcgdGhlIGBwbG90UENBKClgIGZ1bmN0aW9uIHVzaW5nICpudG9wPTEwKiBhbmQNCippbnRncm91cD0ic2V4IiouPC9xPg0KDQpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NX0NCnBsb3RQQ0EodnN0KGRkcyksIG50b3A9MTAsIGludGdyb3VwPSJzZXgiKQ0KYGBgDQoNCkFsdGhvdWdoIHNleC1hc3NvY2lhdGVkIGdlbmUgZXhwcmVzc2lvbiBpcyBub3QgZXhjbHVzaXZlbHkgcmVzdHJpY3RlZCB0byBzZXgNCmNocm9tb3NvbWVzLCBpdCBpcyBvZiBhIG11Y2ggZ3JlYXRlciBtYWduaXR1ZGUgaW4gdGhlIHNleCBjaHJvbW9zb21lcy4NCg0KIyMgQ2hlY2sgc2V4IGFzc2lnbm1lbnQNCg0KV2UgY2FuIHVzZSB0aGUgc3Ryb25nIGFzc29jaWF0aW9uIGJldHdlZW4gc2V4LWNocm9tb3NvbWUgZ2VuZXMgYW5kIHNleCB0byBhc3Nlc3MNCnBvdGVudGlhbCBtaXMtbGFiZWxpbmcgb2Ygc2FtcGxlcy4gQSBxdWljayB3YXkgaXMgdG8gdXNlIGFsbCBnZW5lcyBsb2NhdGVkIGluDQp0aGUgWSBjaHJvbW9zb21lIHRvIHJ1biBhIFBDQS4NCg0KPHE+UGxvdCBhbGwgdGhlIHNhbXBsZXMgaW4gdGhlIHR3byBwcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aGUgUENBIG9mIHRoZQ0KWS1sb2NhdGVkIGdlbmVzLCBjb2xvdXJpbmcgdGhlIHBvaW50cyBieSB0aGUgc2V4IG9mIHRoZSBzYW1wbGUuPC9xPiBOT1RFOiB1c2UNCnRoZSBgdnN0KClgIGZ1bmN0aW9uIG9uIHRoZSB3aG9sZSBkYXRhc2V0LCBzdWJzZXQgYWZ0ZXIuDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KWWdlbmVzIDwtIGdlbmVOYW1lcyAlPiUgZmlsdGVyKHNlcW5hbWVzID09ICJZIiwgZ2VuZV9iaW90eXBlID09ICJwcm90ZWluX2NvZGluZyIpICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKSAlPiUgdW5pcXVlDQp2c3QoZGRzKVtyb3duYW1lcyhkZHMpICVpbiUgWWdlbmVzLF0gJT4lIHBsb3RQQ0EobnRvcD1JbmYsIGludGdyb3VwPSJzZXgiKQ0KYGBgDQoNCkFzIHlvdSBjYW4gc2VlLCBtb3N0IG9mIHRoZSB2YXJpYW5jZSBpcyBleHBsYWluZWQgYnkgdGhlIGZpcnN0IHByaW5jaXBhbA0KY29tcG9uZW50LCBhcyBleHBlY3RlZC4gSXQgd291bGQgaW4gZmFjdCBiZSBlbm91Z2ggdG8gcGxvdCB0aGUgdmFsdWVzIGZvciB0aGUNClBDMS4NCg0KYGBgDQpjb3JyZWN0X3NpZ24gPC0gZnVuY3Rpb24oeCkgew0KICAgIGlmIChtZWRpYW4oeCk8MCkgcmV0dXJuKC0xKngpDQogICAgZWxzZSByZXR1cm4oeCkNCn0NCnBjYTEgPC0gdnN0KGRkcylbcm93bmFtZXMoZGRzKSAlaW4lIFlnZW5lcyxdICU+JSANCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KcGNhMSR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxKSAlPiUNCiAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKSAlPiUNCiAgICBsZWZ0X2pvaW4oYXNfdGliYmxlKGNvbERhdGEoZGRzKSkpICU+JQ0KICAgIGdncGxvdChhZXMoeD1zZXgsIHk9UEMxKSkgKw0KICAgIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArDQogICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91cj1zZXgpLCBoZWlnaHQ9MCwgd2lkdGg9LjIpDQpgYGANCg0KYGBge3J9DQpjb3JyZWN0X3NpZ24gPC0gZnVuY3Rpb24oeCkgew0KICAgIGlmIChtZWRpYW4oeCk8MCkgcmV0dXJuKC0xKngpDQogICAgZWxzZSByZXR1cm4oeCkNCn0NCnBjYTEgPC0gdnN0KGRkcylbcm93bmFtZXMoZGRzKSAlaW4lIFlnZW5lcyxdICU+JSANCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KcGNhMSR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxKSAlPiUNCiAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKSAlPiUNCiAgICBsZWZ0X2pvaW4oYXNfdGliYmxlKGNvbERhdGEoZGRzKSkpICU+JQ0KICAgIGdncGxvdChhZXMoeD1zZXgsIHk9UEMxKSkgKw0KICAgIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArDQogICAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91cj1zZXgpLCBoZWlnaHQ9MCwgd2lkdGg9LjIpDQpgYGANCg0KRm9yIHNpbXBsaWNpdHksIHdlIGFyZSBnb2luZyB0byByZW1vdmUgZnJvbSB0aGUgYGRkc2Agb2JqZWN0IGFsbCBnZW5lcyBtYXBwZWQNCnRvIHRoZSBzZXggY2hyb21vc29tZXMuDQoNCjxxPlJlbW92ZSBhbGwgWCBhbmQgWSBnZW5lcyBmcm9tIHRoZSBgZGRzYCBvYmplY3QuPC9xPg0KDQpgYGB7cn0NCnNleC5nZW5lcyA8LSBnZW5lTmFtZXMgJT4lIGZpbHRlcihzZXFuYW1lcyAlaW4lIGMoIlgiLCJZIikpICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKSAlPiUgdW5pcXVlDQpkaW0oZGRzKQ0KZGRzIDwtIGRkc1shcm93bmFtZXMoZGRzKSAlaW4lIHNleC5nZW5lcyxdDQpkaW0oZGRzKQ0KYGBgDQoNCjxxPlJlY2FsY3VsYXRlIHRoZSBwYWlyd2lzZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiBzYW1wbGVzIGFzIGJlZm9yZSB3aXRoIHRoZQ0KZmlsdGVyZWQgZGF0YXNldC4gUGxvdCB0aGUgdmFsdWVzIGFuZCBwbG90IHRoZSBQQ0Egd2l0aCBudG9wPTEwMDA8L3E+DQoNCmBgYHtyfQ0Kbm9ybV9leHByIDwtIHZzdChkZHMpICU+JSBhc3NheQ0KdG9wMTAwMC5nZW5lcyA8LSB0aWJibGUoZ2VuZV9uYW1lPXJvd25hbWVzKG5vcm1fZXhwciksIHNkPW5vcm1fZXhwciAlPiUgcm93U2RzICkgJT4lDQogICAgc2xpY2VfbWF4KHNkLCBuPTEwMDApICU+JQ0KICAgIHB1bGwoZ2VuZV9uYW1lKQ0KQ29ycyA8LSBjb3Iobm9ybV9leHByW3RvcDEwMDAuZ2VuZXMsIF0pDQptZWRDb3JzIDwtIHJvd01lZGlhbnMoQ29ycykNCm5hbWVzKG1lZENvcnMpIDwtIHJvd25hbWVzKENvcnMpDQptZWRDb3JzICU+JSBzb3J0DQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QodGliYmxlKG1lZENvcnMpKSArIGdlb21fYm94cGxvdChhZXMoeD0xLCB5PW1lZENvcnMpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KcGxvdFBDQSh2c3QoZGRzKSkNCmBgYA0KDQpJbiBvcmRlciB0byBwbG90IHRoZSBzYW1wbGUgaWRzLCB3ZSBuZWVkIHRvIHVzZSB0aGUgInJldHVybkRhdGE9VFJVRSIgb3B0aW9uLA0KaW4gdGhlIGBwbG90UENBKClgIGZ1bmN0aW9uIHdoaWNoLCBpbnN0ZWFkIG9mIHBsb3R0aW5nLCB3aWxsIHRoZW4gcmV0dXJucyB0aGUNCmRhdGEgaW4gYSBmb3JtYXQgdGhhdCBjYW4gYmUgZWFzaWx5IHVzZWQgd2l0aCBnZ3Bsb3QyLCBhZGRpbmcgYSBsYXllciB3aXRoDQp0aGUgbGFiZWxzOg0KDQogICAgZ2dwbG90KGRhdGEpICsNCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsPW5hbWUpLCBudWRnZV95PTEuNSwgc2l6ZT0zKQ0KDQpXZSBjb3VsZCBhbHNvLCBvZiBjb3Vyc2UsIHJ1biB0aGUgUENBIG91cnNlbHZlcyB1c2luZyBgcHJjb21wKClgLg0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0NCnBsb3RQQ0EodnN0KGRkcyksIHJldHVybkRhdGE9VFJVRSkgJT4lDQogIGdncGxvdChhZXMoeD1QQzEsIHk9UEMyKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPWNvbmRpdGlvbikpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uYW1lKSwgbnVkZ2VfeT0xLjUsIHNpemU9MykNCmBgYA0KDQpFdmFsdWF0aW5nIHRoZSBQQ0EgcGxvdCBhbmQgdGhlIGhlYXRtYXAgb2YgY29ycmVsYXRpb25zLCBkbyB5b3UgdGhpbmsgaXQgaXMNCmp1c3RpZmllZCB0byByZW1vdmUgYW55IG9mIHRoZSBzYW1wbGVzPw0KDQoNCg0KDQoNCg0KIyBDZWxsIHR5cGUgZXN0aW1hdGlvbg0KDQpCZWZvcmUgcHJvY2VlZGluZyB3aXRoIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcywgc2luY2Ugd2UgYXJlDQpkZWFsaW5nIHdpdGggaHVtYW4gYnJhaW4gdGlzc3VlLCB3ZSBuZWVkIHRvIGludmVzdGlnYXRlIHdoYXQgdGhlIGNlbGwNCmNvbXBvc2l0aW9uIGlzIGluIG91ciBzYW1wbGVzLiBUaGlzIGlzIG9mIGZ1bmRhbWVudGFsIGltcG9ydGFuY2UgaW4gdGhlc2UgdHlwZQ0Kb2YgZGF0YXNldHMgYmVjYXVzZSAqKm1vc3Qgb2YgdGhlIHZhcmlhYmlsaXR5IGluIGdlbmUgZXhwcmVzc2lvbiBpbiBleHBsYWluZWQNCmJ5IHRoZSBjZWxsIHR5cGVzKiouDQoNCldlIHdpbGwgdXNlIGEgdmVyeSBzaW1wbGUgYXBwcm9hY2ggdG8gZXN0aW1hdGUgY2VsbCB0eXBlcyBzaW1pbGFyLCBpbiBhIHdheSwgdG8NCndoYXQgd2UgdXNlZCB0byAiZXN0aW1hdGUiIHNleDogc3VtbWFyaXplIGdlbmUgZXhwcmVzc2lvbiBvZiBhIHNldCBvZiBzZWxlY3RlZA0KbWFya2VycyBhcyB0aGVpciBmaXJzdCBwcmluY2lwYWwgY29tcG9uZW50IChQQzEpLiBUaGUgbWFya2VyIGdlbmVzIHdpbGwgYmUNCmtub3duIGNlbGwgdHlwZS1zcGVjaWZpYyBnZW5lcy4NCg0KTGV0J3MgbG9hZCBhIGxpc3Qgb2YgbWFya2VycyBvZiBjb3J0aWNhbCBjZWxsIHR5cGVzIGZyb20NCltOZXVyb2VzcHJlc3NvXShodHRwczovL3d3dy5lbmV1cm8ub3JnL2NvbnRlbnQvNC82L0VORVVSTy4wMjEyLTE3LjIwMTcpOg0KDQogICAgY29ydGljYWwubWFya2VycyA8LSByZWFkX3RzdigiLi9EYXRhL2NvcnRpY2FsX21hcmtlcnMudHN2IikNCg0KPHE+UmVhZCB0aGUgdGFiLXNlcGFyYXRlZCBmaWxlICJjb3J0aWNhbF9tYXJrZXJzLnRzdiIgaW50byBhIHRpYmJsZSA8L3E+DQoNCmBgYHtyfQ0KY29ydGljYWwubWFya2VycyA8LSByZWFkX3RzdigiLi9EYXRhL2NvcnRpY2FsX21hcmtlcnMudHN2IikNCmBgYA0KDQpOb3cgbGV0IHVzIHJ1biBhIFBDQSAoYXMgd2UgaGF2ZSBkb25lIHVudGlsIG5vdykgYnV0IHN1YnNldHRpbmcgdGhlIGdlbmVzIHRvDQp0aGUgbmV1cm9uYWwgbWFya2Vycy4gV2Ugd2lsbCB1c2UgdGhlIGBwcmNvbXAoKWAgZnVuY3Rpb24gYW5kIGV4dHJhY3QgdGhlDQp2YWx1ZXMgb2YgdGhlIFBDMSBmb3IgZWFjaCBzYW1wbGUuDQoNCjxxPlJ1biBhIFBDQSB1c2luZyBgcHJjb21wKClgIG9ubHkgb24gbmV1cm9uYWwgbWFya2Vycy4gRXhwbG9yZSB0aGUgcmVzdWx0IGFuZA0KZmluZCB3aGVyZSB0aGUgY29vcmRpbmF0ZXMgZm9yIHRoZSBmaXJzdCBQQyBhcmUuPC9xPg0KDQpgYGB7cn0NCmVzdGltYXRlX2N0IDwtIGZ1bmN0aW9uKG9iamVjdCwgZ2VuZXMsIGN0PSJQQzEiLCByZXNjYWxlPVRSVUUpIHsNCiAgICBvYmplY3QudnN0IDwtIHZzdChvYmplY3QpDQogICAgcGNhIDwtIG9iamVjdC52c3Rbcm93bmFtZXMob2JqZWN0LnZzdCkgJWluJSBnZW5lcyxdICU+JQ0KICAgICAgICBhc3NheSgpICU+JQ0KICAgICAgICB0KCkgJT4lDQogICAgICAgIHByY29tcCgpDQogICAgcmVzdWx0IDwtIGFzX3RpYmJsZShwY2EkeCwgcm93bmFtZXM9InNhbXBsZV9pZCIpICU+JQ0KICAgICAgICBzZWxlY3Qoc2FtcGxlX2lkLCBQQzEpICU+JQ0KICAgICAgICBtdXRhdGUoUEMxPWNvcnJlY3Rfc2lnbihQQzEpKQ0KICAgIGNvbG5hbWVzKHJlc3VsdCkgPC0gYygic2FtcGxlX2lkIiwgY3QpDQogICAgaWYgKHJlc2NhbGUpIHJlc3VsdFssY3RdIDwtIHNjYWxlKHJlc3VsdFssY3RdKVssMV0NCiAgICByZXR1cm4ocmVzdWx0KQ0KfQ0KbmV1cm9uYWwuZXN0IDwtIGVzdGltYXRlX2N0KGRkcywgY29ydGljYWwubWFya2VycyAlPiUgZmlsdGVyKGNlbGxfdHlwZT09Ik5ldXJvbiIpICU+JSBwdWxsKGdlbmVfbmFtZSksIGN0PSJOZXVyb24iKQ0KYGBgDQoNCk5vdyB3ZSBjYW4gZG8gdGhhdCBmb3IgZXZlcnkgb3RoZXIgY29ydGljYWwgY2VsbCB0eXBlLg0KDQo8cT5DYWxjdWxhdGUgdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQgZm9yIHRoZSBvdGhlciBjZWxsIHR5cGVzIGFuZCBhZGQNCnRoZW0gYWxsIHRvIHRoZSBtZXRhZGF0YXRhIG9mIHRoZSBkZHMgb2JqZWN0IChgY29sRGF0YShkZHMpYCkuPC9xPg0KDQpgYGB7cn0NCmN0LmVzdCA8LSBsYXBwbHkodW5pcXVlKGNvcnRpY2FsLm1hcmtlcnMkY2VsbF90eXBlKSwgZnVuY3Rpb24oY3Qpew0KICAgIG1lc3NhZ2UocGFzdGUwKGN0LCAiLi4uIikpDQogICAgR2VuZXMgPC0gZmlsdGVyKGNvcnRpY2FsLm1hcmtlcnMsIGNlbGxfdHlwZT09Y3QpICU+JSBwdWxsKGdlbmVfbmFtZSkNCiAgICBlc3RpbWF0ZV9jdChkZHMsIGdlbmVzPUdlbmVzLCBjdD1jdCkNCn0pICU+JSBSZWR1Y2UoImxlZnRfam9pbiIsIC4pDQoNCm5ld0NvbERhdGEgPC0gbGVmdF9qb2luKGFzX3RpYmJsZShjb2xEYXRhKGRkcykpLCBjdC5lc3QsIGJ5PSJzYW1wbGVfaWQiKSAlPiUNCiAgICBhcygiRGF0YUZyYW1lIikNCnJvd25hbWVzKG5ld0NvbERhdGEpIDwtIG5ld0NvbERhdGEkc2FtcGxlX2lkDQphbGwocm93bmFtZXMoY29sRGF0YShkZHMpKSA9PSByb3duYW1lcyhuZXdDb2xEYXRhKSkNCmNvbERhdGEoZGRzKSA8LSBuZXdDb2xEYXRhDQpgYGANCg0KQW5kIGZpbmFsbHksIGRvIHRoZSBzYW1lIHdpdGggdGhlIFJOQSBtYXJrZXJzIGZvciB0aGUgY29ydGljYWwgc3luYXBzb21lLCB3aGljaA0KYXJlIG5vdCBxdWl0ZSB0aGUgc2FtZSBhcyB0aGUgc2V0IG9mIG5ldXJvbmFsIG1hcmtlcnMuIFRoZXNlIGFyZSBSTkFzIGZvdW5kIGluDQp0aGUgc3luYXBzZXMsIG9idGFpbmVkIGZyb20gW0hhZm5lciBldCBhbC4NCjIwMTldKGh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvMzEwOTc2MzkvKS4NCg0KICAgIHN5bmFwdG9zb21lLm1hcmtlcnMgPC0gcmVhZF90c3YoIi4vRGF0YS9zeW5hcHRvc29tZV9tYXJrZXJzLnRzdiIpDQogICAgDQpgYGB7cn0NCiNzeW4gPC0gcXVzYWdlOjpyZWFkLmdtdCgiLi9EYXRhL3N5bmFwdG9zb21lX3N5bWJvbHMuZ210IikgJT4lIHFkYXBUb29sczo6bGlzdDJkZigpICU+JSBhc190aWJibGUNCiNjb2xuYW1lcyhzeW4pIDwtIGMoImdlbmVfaWQiLCAiY2VsbF90eXBlIikNCiN3cml0ZV90c3Yoc3luLCAiLi9EYXRhL3N5bmFwdG9zb21lX21hcmtlcnMudHN2IikNCnN5bmFwdG9zb21lLm1hcmtlcnMgPC0gcmVhZF90c3YoIi4vRGF0YS9zeW5hcHRvc29tZV9tYXJrZXJzLnRzdiIpDQoNCnN5bi5lc3QgPC0gZXN0aW1hdGVfY3QoZGRzLCBnZW5lcz11bmlxdWUoc3luYXB0b3NvbWUubWFya2VycyRnZW5lX2lkKSwgY3Q9InN5bmFwdG9zb21lIikNCmFsbChzeW4uZXN0JHNhbXBsZV9pZCA9PSBjb2xEYXRhKGRkcykkc2FtcGxlX2lkKQ0KY29sRGF0YShkZHMpJFN5bmFwc2VzIDwtIHN5bi5lc3Qkc3luYXB0b3NvbWUNCmNvbERhdGEoZGRzKQ0KYGBgDQoNCkhvdyBjYW4gd2UgYXNzZXNzIHdoaWNoIHZhcmlhYmxlcyBhcmUgcmVzcG9uc2libGUgZm9yIG1vc3Qgb2YgdGhlIHZhcmlhdGlvbiBpbg0KZ2VuZSBleHByZXNzaW9uPyBQQ0EgaXMgdmVyeSBoZWxwZnVsIGluIHRoaXMgcmVnYXJkcy4gQnkgZGVmaW5pdGlvbiwgdGhlIGZpcnN0DQpwcmluY2lwYWwgY29tcG9uZW50IHdpbGwgbWF4aW1pemUgdGhlIHZhcmlhbmNlIHRoYXQgaXQgZXhwbGFpbnMgYnkgY29tYmluaW5nDQpsaW5lYXJseSB0aGUgdmFyaWFibGVzLiBJbiBhIHdheSwgaXQgaXMgInN1bW1hcml6aW5nIiBnZW5lIGV4cHJlc3Npb24gYnkNCm9wdGltYWxseSBwcm9qZWN0aW5nIGl0IGludG8gb25lIGRpbWVuc2lvbi4gVGhlbiwgd2l0aCB0aGUgbGVmdG92ZXIgdmFyaWFuY2UNCm5vdCBleHBsYWluZWQgYnkgUEMxLCBpdCB3aWxsIGRvIHRoZSBzYW1lLCBidXQgd2l0aCB0aGUgY29uc3RhaW50IHRoYXQgUEMyDQpuZWVkcyB0byBiZSBwZXJwZW5kaWN1bGFyIHRvIFBDMS4NCg0KVGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnRzIGFyZSBvZnRlbiByZWZlcnJlZCB0byBhcyB0aGUgbWFpbiBsaW5lcyBvZg0KdmFyaWF0aW9uLCBiZWNhdXNlIHRoZXkgZ2F0aGVyIGFzIG11Y2ggYXMgcG9zc2libGUsIGFuZCBvZnRlbiBzZXJ2ZSBhcyBhIGdvb2QNCiJzdW1tYXJ5IiBvZiB0aGUgbXVsdGlkaW1lbnNpb25hbCBkYXRhLg0KDQpJZiB3ZSB3YW50IHRvIGFzc2VzcyB3aGV0aGVyIGNlbGwgdHlwZSBjb21wb3NpdGlvbiAob3Igb3RoZXIgdmFyaWFibGVzKSBleHBsYWluDQptb3N0IG9mIHRoZSB2YXJpYXRpb24sIGEgc2ltcGxlIHdheSBjb3VsZCBiZSB0byB0ZXN0IGZvciBhc3NvY2lhdGlvbiBiZXR3ZWVuDQp2YXJpYWJsZXMgYW5kIFBDcy4gVGhpcyBpcyBhIGJpdCBpbnZvbHZlZCBwcm9ncmFtbWF0aWNhbGx5LCBzbyBoZXJlIGlzIHRoZQ0KY29kZToNCg0KYGBgDQpzY2FsZS52ZWMgPC0gZnVuY3Rpb24oeCkgc2NhbGUoeClbLDFdDQoNCnBjYTIgPC0gZGRzICU+JSANCiAgICB2c3QgJT4lDQogICAgYXNzYXkgJT4lDQogICAgdCAlPiUNCiAgICBwcmNvbXANCg0KTWV0YWRhdGEucGNhIDwtIHBjYTIkeCAlPiUNCiAgICBhc190aWJibGUocm93bmFtZXM9InNhbXBsZV9pZCIpICU+JQ0KICAgIHNlbGVjdChzYW1wbGVfaWQsIFBDMTpQQzUpICU+JQ0KICAgIG11dGF0ZV9pZihpcy5udW1lcmljLCBzY2FsZS52ZWMpICU+JQ0KICAgIGxlZnRfam9pbihhc190aWJibGUoY29sRGF0YShkZHMpKSkNCg0KTWV0YWRhdGEucGNhIDwtIE1ldGFkYXRhLnBjYSAlPiUgc2VsZWN0KC1zYW1wbGVfaWQsIC1vcmlnaW4sIC1zYW1wbGVfaWRfcGFwZXIpICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JQ0KICAgIG11dGF0ZV9pZihpcy5mYWN0b3IsIGFzLmludGVnZXIpDQoNCmFsbENvcnMgPC0gbGFwcGx5KHBhc3RlMCgiUEMiLCAxOjUpLCBmdW5jdGlvbihwYykgew0KICAgIGxhcHBseShjb2xuYW1lcyhNZXRhZGF0YS5wY2EpWzY6bmNvbChNZXRhZGF0YS5wY2EpXSwgZnVuY3Rpb24odmFyKXsNCiAgICAgICAgI21lc3NhZ2UocGFzdGUwKHBjLCAiIHZzICIsIHZhcikpDQogICAgICAgIGxtKGZvcm11bGE9YXMuZm9ybXVsYShwYXN0ZTAocGMsICJ+IiwgdmFyKSksIGRhdGE9TWV0YWRhdGEucGNhKSAlPiUNCiAgICAgICAgICAgIGJyb29tOjp0aWR5KCkgJT4lIGZpbHRlcih0ZXJtIT0iKEludGVyY2VwdCkiKSAlPiUNCiAgICAgICAgICAgIG11dGF0ZShQQz1wYywgVkFSPXZhcikNCiAgICB9KSAlPiUgUmVkdWNlKCJiaW5kX3Jvd3MiLC4pDQp9KSAlPiUgUmVkdWNlKCJiaW5kX3Jvd3MiLC4pDQoNClB2YWxzIDwtIGFsbENvcnMgJT4lIHNlbGVjdChwLnZhbHVlLCBQQywgVkFSKSAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tPVZBUiwgdmFsdWVzX2Zyb209cC52YWx1ZSkNCnAubWF0IDwtIGFzLm1hdHJpeChQdmFsc1ssLTFdKQ0Kcm93bmFtZXMocC5tYXQpIDwtIFB2YWxzJFBDDQoNCkVzdGltYXRlcyA8LSBhbGxDb3JzICU+JSBzZWxlY3QoZXN0aW1hdGUsIFBDLCBWQVIpICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209VkFSLCB2YWx1ZXNfZnJvbT1lc3RpbWF0ZSkNCmUubWF0IDwtIGFzLm1hdHJpeChFc3RpbWF0ZXNbLC0xXSkNCnJvd25hbWVzKGUubWF0KSA8LSBFc3RpbWF0ZXMkUEMNCg0KY29ycnBsb3Q6OmNvcnJwbG90KGUubWF0LCBwLm1hdD1wLm1hdCkNCmBgYA0KDQpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NH0NCnNjYWxlLnZlYyA8LSBmdW5jdGlvbih4KSBzY2FsZSh4KVssMV0NCg0KcGNhMiA8LSBkZHMgJT4lIA0KICAgIHZzdCAlPiUNCiAgICBhc3NheSAlPiUNCiAgICB0ICU+JQ0KICAgIHByY29tcA0KDQpNZXRhZGF0YS5wY2EgPC0gcGNhMiR4ICU+JQ0KICAgIGFzX3RpYmJsZShyb3duYW1lcz0ic2FtcGxlX2lkIikgJT4lDQogICAgc2VsZWN0KHNhbXBsZV9pZCwgUEMxOlBDNSkgJT4lDQogICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHNjYWxlLnZlYykgJT4lDQogICAgbGVmdF9qb2luKGFzX3RpYmJsZShjb2xEYXRhKGRkcykpKQ0KDQpNZXRhZGF0YS5wY2EgPC0gTWV0YWRhdGEucGNhICU+JSBzZWxlY3QoLXNhbXBsZV9pZCwgLW9yaWdpbiwgLXNhbXBsZV9pZF9wYXBlcikgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikgJT4lDQogICAgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMuaW50ZWdlcikNCg0KYWxsQ29ycyA8LSBsYXBwbHkocGFzdGUwKCJQQyIsIDE6NSksIGZ1bmN0aW9uKHBjKSB7DQogICAgbGFwcGx5KGNvbG5hbWVzKE1ldGFkYXRhLnBjYSlbNjpuY29sKE1ldGFkYXRhLnBjYSldLCBmdW5jdGlvbih2YXIpew0KICAgICAgICAjbWVzc2FnZShwYXN0ZTAocGMsICIgdnMgIiwgdmFyKSkNCiAgICAgICAgbG0oZm9ybXVsYT1hcy5mb3JtdWxhKHBhc3RlMChwYywgIn4iLCB2YXIpKSwgZGF0YT1NZXRhZGF0YS5wY2EpICU+JQ0KICAgICAgICAgICAgYnJvb206OnRpZHkoKSAlPiUgZmlsdGVyKHRlcm0hPSIoSW50ZXJjZXB0KSIpICU+JQ0KICAgICAgICAgICAgbXV0YXRlKFBDPXBjLCBWQVI9dmFyKQ0KICAgIH0pICU+JSBSZWR1Y2UoImJpbmRfcm93cyIsLikNCn0pICU+JSBSZWR1Y2UoImJpbmRfcm93cyIsLikNCg0KUHZhbHMgPC0gYWxsQ29ycyAlPiUgc2VsZWN0KHAudmFsdWUsIFBDLCBWQVIpICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209VkFSLCB2YWx1ZXNfZnJvbT1wLnZhbHVlKQ0KcC5tYXQgPC0gYXMubWF0cml4KFB2YWxzWywtMV0pDQpyb3duYW1lcyhwLm1hdCkgPC0gUHZhbHMkUEMNCg0KRXN0aW1hdGVzIDwtIGFsbENvcnMgJT4lIHNlbGVjdChlc3RpbWF0ZSwgUEMsIFZBUikgJT4lDQogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbT1WQVIsIHZhbHVlc19mcm9tPWVzdGltYXRlKQ0KZS5tYXQgPC0gYXMubWF0cml4KEVzdGltYXRlc1ssLTFdKQ0Kcm93bmFtZXMoZS5tYXQpIDwtIEVzdGltYXRlcyRQQw0KDQpjb3JycGxvdDo6Y29ycnBsb3QoZS5tYXQsIHAubWF0PXAubWF0KQ0KYGBgDQoNCkRvIHlvdSBzZWUgYW55dGhpbmcgaW50ZXJlc3Rpbmc/IE1heSBpdCBiZSB0aGF0IHRoZSBSTkEgcXVhbGl0eSAoUklOKSBpcw0KYXNzb2NpYXRlZCB3aXRoIGNlbGwgdHlwZSBjb21wb3NpdGlvbj8gVGhhdCBjYW4gZWFzaWx5IGJlIHRlc3RlZCB3aXRoIGEgcXVpY2sNCmxpbmVhciByZWdyZXNzaW9uOiB0cnkgcHJlZGljdGluZyB0aGUgUklOIHZhbHVlcyB3aXRoIHRoZSBlc3RpbWF0ZWQgdmFsdWVzIGZvcg0Kc3luYXB0b3NvbWFsIGNvbnRlbnQsIHRoZW4gdHJ5IHRoZSBzYW1lIHdpdGggdGhlIGVzdGltYXRlZCBuZXVyb25hbCBjb250ZW50LA0KYW5kIHRoZW4gd2l0aCBib3RoIGF0IHRoZSBzYW1lIHRpbWUgaW4gdGhlIG1vZGVsLiBUaGUgZm9ybXVsYXMgYXJlIGFzIGZvbGxvdzoNCg0KKiByaW4gfiBTeW5hcHNlcw0KKiByaW4gfiBOZXVyb24NCiogcmluIH4gU3luYXBzZXMgKyBOZXVyb24NCg0KVGhlbiBjb21wYXJlIHRoZSByZXN1bHRpbmcgbW9kZWxzIHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbiBvbiB0aGUgbGluZWFyDQpmaXRzLg0KDQo8cT5SdW4gMyBsaW5lYXIgcmVncmVzc2lvbnMgd2l0aCByaW4gYXMgYSByZXNwb25zZSB2YXJpYWJsZSBhbmQgMS4gU3luYXBzZXM7IDIuDQpOZXVyb247IGFuZCAzLiBTeW5hcHNlcyArIE5ldXJvbiBhcyBwcmVkaWN0b3JzLiBDaGVjayB0aGUgYWRqdXN0ZWQgUi1zcXVhcmVkDQp2YWx1ZXMgdG8gY29tcGFyZSB0aGUgbW9kZWxzLjwvcT4NCg0KYGBge3J9DQojIFJJTg0KbG0uc3luIDwtIGxtKHJpbn5TeW5hcHNlcywgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQpsbS5uZXUgPC0gbG0ocmlufk5ldXJvbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQpsbS5ib3RoIDwtIGxtKHJpbn5OZXVyb24rU3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KbG0uYm90aC5jb2hvcnQgPC0gbG0ocmlufk5ldXJvbitTeW5hcHNlcytvcmlnaW4sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KbG0uc3luICU+JSBzdW1tYXJ5DQpsbS5uZXUgJT4lIHN1bW1hcnkNCmxtLmJvdGggJT4lIHN1bW1hcnkNCmxtLmJvdGguY29ob3J0ICU+JSBzdW1tYXJ5DQpgYGANCg0KU3luYXB0b3NvbWUgZXhwcmVzc2lvbiBzZWVtIHRvIGV4cGxhaW4gUklOIHRoZSBiZXN0LiBCdXQgd2hhdCBpZiB3ZSBjb25zaWRlcg0KIm9yaWdpbiIgYXMgd2VsbD8gV2hpY2ggaXMgdGhlICJiZXN0IiBtb2RlbD8gQ2hlY2sgdGhlIGFkanVzdGVkIFIgc3F1YXJlZCBmb3INCnRoZSBtb2RlbHMgb3IgcnVuIGBhbm92YSgpYCBvbiB0d28gbW9kZWxzIHRvIHRlc3Qgd2hldGhlciBhIG1vcmUgY29tcGxleCBtb2RlbA0KaXMgc2lnbmlmaWNhbnRseSBiZXR0ZXIgYXQgY2FwdHVyaW5nIFJJTiB2YXJpYXRpb24uDQoNCjxxPkFkZCAib3JpZ2luIiBhcyBhIGNvdmFyaWF0ZSB0byB0aGUgcHJldmlvdXMgbW9kZWxzIGFuZCBjb21wYXJlIHRoZSBhZGp1c3RlZA0KUiBzcXVhcmVkIGJldHdlZW4gdGhlIHNpbXBsZXIgYW5kIG1vcmUgY29tcGxlcyB2ZXJzaW9ucyBvZiB0aGUgbW9kZWwuIEV2ZW4NCmJldHRlciwgdXNlIHRoZSBgYW5vdmEoKWAgZnVuY3Rpb24gdG8gY29tcGFyZSB0aGUgbW9kZWxzLjwvcT4NCg0KYGBge3J9DQphbm92YSgNCiAgICBsbShyaW5+U3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKSwgDQogICAgbG0ocmluflN5bmFwc2VzK05ldXJvbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQopDQphbm92YSgNCiAgICBsbShyaW5+U3luYXBzZXMsIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKSwgDQogICAgbG0ocmluflN5bmFwc2VzK29yaWdpbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpDQopDQphbm92YSgNCiAgICBsbShyaW5+TmV1cm9uLCBkYXRhPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpKSksDQogICAgbG0ocmlufk5ldXJvbitvcmlnaW4sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KKQ0KYW5vdmEoDQogICAgbG0ocmluflN5bmFwc2VzK29yaWdpbiwgZGF0YT1hcy5kYXRhLmZyYW1lKGNvbERhdGEoZGRzKSkpLA0KICAgIGxtKHJpbn5TeW5hcHNlcytOZXVyb24rb3JpZ2luLCBkYXRhPWFzLmRhdGEuZnJhbWUoY29sRGF0YShkZHMpKSkNCikNCmxtLmZpbmFsIDwtIGxtKHJpbn5TeW5hcHNlcytOZXVyb24sIGRhdGE9YXMuZGF0YS5mcmFtZShjb2xEYXRhKGRkcykpKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyBjaGVjayB0aGUgZGlhZ25vc3RpYyBwbG90cyBieSBzaW1wbHkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBwbG90KClgDQpvbiB0aGUgbGluZWFyIGZpdC4gRG8geW91IHNlZSBhIHNhbXBsZSB0aGF0IHlvdSBtYXkgd2FudCB0byBleGNsdWRlIGJlZm9yZQ0KcnVubmluZyB5b3VyIG1vZGVsIGFnYWluPyBEb2VzIGl0IG1ha2UgYSBkaWZmZXJlbmNlPw0KDQpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NX0NCnBsb3QobG0uZmluYWwpDQpgYGANCg0KDQoNCg0KIyBEaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uDQoNCiMjIFJ1bm5pbmcgYW4gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMNCg0KV2l0aCB0aGUgYERFU2VxMmAgcGFja2FnZSwgd2UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYERFU2VxKClgIG9uIG91ciBgZGRzYA0Kb2JqZWN0IHRvIHJ1biB0aGUgd2hvbGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcGlwZWxpbmUsIHdoaWNoIHdpbGwgcnVuIHRocmVlDQpzdGVwcyBpbiBvcmRlcjoNCg0KMS4gYGVzdGltYXRlU2l6ZUZhY3RvcnMoZGRzKWA6IGVzdGltYXRpb24gb2Ygc2FtcGxlLXNwZWNpZmljIG5vcm1hbGl6YXRpb24gcGFyYW1ldGVycw0KMi4gYGVzdGltYXRlRGlzcGVyc2lvbnMoZGRzKWA6IGVzdGltYXRpb24gb2YgZ2VuZS1zcGVjaWZpYyBkaXNwZXJzaW9uIHBhcmFtZXRlcnMNCjMuIGBuYmlub21XYWxkVGVzdChkZHMpYDogbmVnYXRpdmUgYmlub21pYWwgZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIHRvIGNhbGN1bGF0ZSB0aGUgZGVzaXJlZCBsb2cyLWZvbGQgY2hhbmdlcyBhbmQgY2FsY3VsYXRpb24gb2YgV2FsZCBzdGF0aXN0aWNzDQoNCldoZW4geW91IHJ1biBgREVTZXFgLCBieSBkZWZhdWx0IGl0IHdpbGwgY2FsY3VsYXRlIHRoZSBmaXQgYWNjb3JkaW5nIHRvDQp3aGF0ZXZlciBtb2RlbCB5b3UgaGFkIHNwZWNpZmllZCB3aGVuIHlvdSBjcmVhdGVkIHRoZSBvYmplY3QuIFRoZSBtb2RlbCdzDQpmb3JtdWxhIGNhbiBiZSBwcmludGVkIG9uIHNjcmVlbiB1c2luZyBgZGVzaWduKGRkcylgLg0KDQpMZXQncyBjaGFuZ2UgdGhlIG1vZGVsJ3MgZm9ybXVsYSBvZiB0aGUgb2JqZWN0IGxpa2Ugc286DQoNCiAgICBkZXNpZ24oZGRzKSA8LSB+IHNleCArIGFnZV95ZWFycyArIGNvbmRpdGlvbg0KDQphbmQgdGhlbiBydW4gYERFU2VxKClgIG9uIHRoZSBgZGRzYCBvYmplY3QuDQoNCioqTm90ZSAxOioqIGlmIHlvdSBoYXZlIG1hbnVhbGx5IGNoYW5nZWQgc29tZXRoaW5nIGluIHRoZSBgY29sRGF0YShkZHMpYCwgaXQgbWF5DQpiZSB0aGF0IHNvbWUgY29sdW1ucyBhcmUgb2YgdHlwZSAiY2hhcmFjdGVyIi4gVGhleSBzaG91bGQgYmUgY29udmVydGVkIHRvDQpmYWN0b3JzLg0KDQoqKk5vdGUgMjoqKiB0byBhdm9pZCBzb21lIGhlYWRhY2hlcyBsYXRlciBvbiwgd2Ugd2FudCB0byBiZSB2ZXJ5IGV4cGxpY2l0IHdpdGgNCm91ciB2YXJpYWJsZSBvZiBpbnRlcmVzdCAodGhlIGRpc2Vhc2Ugc3RhdHVzKSBieSBzcGVjaWZ5aW5nIHdoaWNoIG9mIHRoZSB0d28NCmxldmVscyAoImNhc2UiIG9yICJjb250cm9sIikgaXMgdGhlIFJFRkVSRU5DRSB2YWx1ZS4gVGhpcyBjYW4gYmUgZG9uZSBieSB1c2luZw0KdGhlIGByZWxldmVsKClgIGZ1bmN0aW9uIGxpa2Ugc286DQoNCiAgICBjb2xEYXRhKGRkcykkY29uZGl0aW9uIDwtIHJlbGV2ZWwoY29sRGF0YShkZHMpJGNvbmRpdGlvbiwgcmVmPSJDb250cm9sIikNCg0KPHE+UnVuIGEgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgb24gdGhlIGBkZHNgIG9iamVjdCB1c2luZyB0aGUNCmBERVNlcSgpYCBmdW5jdGlvbi4gVGhlIG1vZGVsJ3MgZm9ybXVsYSBzaG91bGQgYmUgX19+IHNleCArIGFnZSArIGNvbmRpdGlvbl9fLg0KT3ZlcndyaXRlIHdpdGggdGhlIHJldHVybmVkIG9iamVjdCB0aGUgYGRkc2Agb2JqZWN0LjwvcT4NCg0KVXNlZnVsIGZ1bmN0aW9uczoNCg0KKiBgbXV0YXRlX2lmKClgDQoqIGBpcy5jaGFyYWN0ZXIoKWANCiogYERFU2VxKClgDQoNCmBgYHtyfQ0KIyBjb252ZXJ0IHRvIGNoYXIgdG8gZmFjdG9yDQpjaHJfY29scyA8LSBzYXBwbHkoY29sRGF0YShkZHMpLCBpcy5jaGFyYWN0ZXIpDQpjb2xEYXRhKGRkcylbY2hyX2NvbHNdIDwtIGxhcHBseShjb2xEYXRhKGRkcylbY2hyX2NvbHNdLCBhcy5mYWN0b3IpDQoNCiMgcmVsZXZlbCBjb25kaXRpb24ganVzdCBpbiBjYXNlDQpjb2xEYXRhKGRkcykkY29uZGl0aW9uIDwtIHJlbGV2ZWwoY29sRGF0YShkZHMpJGNvbmRpdGlvbiwgcmVmPSJDb250cm9sIikNCg0KZGVzaWduKGRkcykgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyBjb25kaXRpb24NCmRkcyA8LSBERVNlcShkZHMpDQpgYGANCg0KIyMgTG9va2luZyBhdCB0aGUgcmVzdWx0cw0KDQpUaGUgYHJlc3VsdHMoKWAgZnVuY3Rpb24gZXh0cmFjdHMgdGhlIGxvZzIgZm9sZCBjaGFuZ2VzIGFuZCBwIHZhbHVlcyBmcm9tIHRoZQ0KYGRkc2Agb2JqZWN0IChhcyBsb25nIGFzIHRoZSBgREVTZXFgIGZ1bmN0aW9uIHdhcyBydW4pLiBCeSBkZWZhdWx0LCB0aGUNCmZ1bmN0aW9uIHdpbGwgZXh0cmFjdCB0aGUgbG9nMiBmb2xkIGNoYW5nZSAqKmZvciB0aGUgbGFzdCB2YXJpYWJsZSBpbiB0aGUNCmRlc2lnbiBmb3JtdWxhKiogKGluIG91ciBjYXNlLCB0aGUgZGlzZWFzZSBzdGF0dXMpLiBIb3dldmVyLCBpdCBpcyBhbHdheXMgZ29vZA0KcHJhY3RpY2UgdG8gZXhwbGljaXRseSBzcGVjaWZ5IHRoZSBjb250cmFzdCB3aGVuIGNhbGxpbmcgYHJlc3VsdHMoKWAgd2l0aCB0aGUNCiJjb250cmFzdCIgb3IgIm5hbWUiIGFyZ3VtZW50LiBUaGVyZSBhcmUgZGlmZmVyZW50IHdheXMgb2Ygc3BlY2lmeWluZyBjb250cmFzdHMNCmJ1dCwgZm9yIGV4YW1wbGUsIGlmIHdlIHdhbnRlZCB0byBleHRyYWN0IHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzDQpiZXR3ZWVuIG1hbGVzIGFuZCBmZW1hbGVzLCB3ZSBjb3VsZCB1c2U6DQoNCiAgICByZXN1bHRzKGRkcywgbmFtZT0ic2V4X01fdnNfRiIpDQoNCm9yDQoNCiAgICByZXN1bHRzKGRkcywgY29udHJhc3Q9Yygic2V4IiwgIk0iLCAiRiIpKQ0KDQp0aGV5IGFyZSBlcXVpdmFsZW50LiBBIG5pY2UgdHJpY2sgdG8gc2VlIHdoaWNoIGNvbnRyYXN0cyBjYW4gYmUgZXh0cmFjdGVkIHVzaW5nDQoibmFtZT0iLCB5b3UgY2FuIHVzZQ0KDQogICAgcmVzdWx0c05hbWVzKGRkcykNCg0Kd2hpY2ggcHJpbnRzIGFsbCBwb3NzaWJsZSB2YWx1ZXMgZm9yIHRoZSBjb250cmFzdCBuYW1lLiBJbiBvdXIgY2FzZSwgdGhlDQpwb3NzaWJsZSB2YWx1ZXMgZm9yIHRoZSAibmFtZT0iIGFyZ3VtZW50IGFyZTogKioiSW50ZXJjZXB0IiwgInNleF9NX3ZzX0YiLA0KImFnZV95ZWFycyIsIGFuZCAiY29uZGl0aW9uX0Nhc2VfdnNfQ29udHJvbCIqKg0KDQo8cT5Vc2UgdGhlIGByZXN1bHRzKClgIGZ1bmN0aW9uIG9uIHRoZSBgZGRzYCBvYmplY3QgdG8gZXh0cmFjdCB0aGUgZGlmZmVyZW50aWFsDQpleHByZXNzaW9uIGJldHdlZW4gY2FzZXMgYW5kIGNvbnRyb2xzIGFuZCBmaW5kIG91dCB3aGV0aGVyIGFueSBnZW5lcyBhcmUNCnNpZ25pZmljYW50IGFmdGVyIGNvcnJlY3Rpb24uPC9xPg0KDQpgYGB7cn0NCiNyZXN1bHRzKGRkcywgbmFtZT0ic2V4X01fdnNfRiIpDQojcmVzdWx0cyhkZHMsIGNvbnRyYXN0PWMoInNleCIsICJNIiwgIkYiKSkNCnJlcyA8LSByZXN1bHRzKGRkcywgbmFtZT0iY29uZGl0aW9uX0Nhc2VfdnNfQ29udHJvbCIpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpDQpyZXMgJT4lIGFycmFuZ2UocGFkaikNCmBgYA0KDQpBcmUgdGhlcmUgYW55IHNpZ25pZmljYW50IGNoYW5nZXMgaW4gZ2VuZSBleHByZXNzaW9uIGFzc29jaWF0ZWQgd2l0aCB0aGUNCmRpc2Vhc2U/IA0KDQpXZSBoYXZlIHNlZW4gdGhhdCBSSU4gaXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBtYWluIGxpbmVzIG9mIHZhcmlhdGlvbiBpbiBnZW5lDQpleHByZXNzaW9uLiBXZSBzaG91bGQgZXhwZWN0LCB0aGVuLCB0aGF0IFJJTiBpcyBhZGRpbmcgYSBsb3Qgb2YgInVuaW50ZXJlc3RpbmciDQp2YXJpYXRpb24gdG8gdGhlIGRhdGFzZXQsIGkuZS4sIGEgbG90IG9mIG5vaXNlLiBXaGF0IGhhcHBlbnMgaWYgeW91IGFkZCAicmluIg0KYXMgYSBjb3ZhcmlhdGU/DQoNCllvdSdsbCBuZWVkIHRvIHVwZGF0ZSB0aGUgZm9ybXVsYSBhbmQgcmUtcnVuIHRoZSB3aG9sZSBwaXBlbGluZSBsaWtlIHNvOg0KDQpgYGANCmRlc2lnbihkZHMpIDwtIH4gc2V4ICsgYWdlX3llYXJzICsgcmluICsgY29uZGl0aW9uDQpkZHMgPC0gREVTZXEoZGRzKQ0KcmVzIDwtIHJlc3VsdHMoZGRzLCBuYW1lPSJjb25kaXRpb25fQ2FzZV92c19Db250cm9sIikgJT4lIGFzX3RpYmJsZShyb3duYW1lcz0iZ2VuZV9uYW1lIikNCnJlcyAlPiUgYXJyYW5nZShwYWRqKQ0KYGBgDQoNCjxxPlJlLXJ1biB0aGUgYW5hbHlzZXMgd2l0aCBSSU4gYXMgYSBjb3ZhcmlhdGUuPC9xPg0KDQpgYGB7cn0NCmRkc19yaW4gPC0gZGRzDQpkZXNpZ24oZGRzX3JpbikgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyByaW4gKyBjb25kaXRpb24NCmRkc19yaW4gPC0gREVTZXEoZGRzX3JpbikNCnJlc19yaW4gPC0gcmVzdWx0cyhkZHNfcmluLCBuYW1lPSJjb25kaXRpb25fQ2FzZV92c19Db250cm9sIikgJT4lIGFzX3RpYmJsZShyb3duYW1lcz0iZ2VuZV9uYW1lIikNCnJlc19yaW4gJT4lIGFycmFuZ2UocGFkaikNCmBgYA0KDQpXaGF0IGRvIHlvdSB0aGluayBoYXMgY2hhbmdlZD8gSG93IGRvZXMgdGhlIGV4dHJhIGNvdmFyaWF0ZSBhZGRlZCBpbmZsdWVuY2UgdGhlDQpyZXN1bHRzPyBXaGF0IGRvIHlvdSB0aGluayB3b3VsZCBoYXZlIGhhcHBlbmVkIGlmIHlvdSBhZGRlZCB0byB0aGUgbW9kZWwgYQ0KY292YXJpYXRlIHRoYXQgd2FzIGhpZ2hseSBhc3NvY2lhdGVkIHdpdGggdGhlIGNvbmRpdGlvbiBvZiBpbnRlcmVzdCAoaS5lLiwNCiJjb25mb3VuZGVkIik/DQoNCg0KU2luY2UgdGhlIGNvaG9ydHMgaGFkIGRpZmZlcmVudCBSSU5zIGFuZCBwb3N0LW1vcnRlbSBpbnRlcnZhbHMsIHRyeSB0byBydW4gdGhlDQpjb2hvcnRzIHNlcGFyYXRlbHkgYW5kIGV4cGxvcmUgdGhlIHJlc3VsdHMuDQoNCjxxPlJ1biB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzZXMgc2VwYXJhdGVseSBmb3IgZWFjaCBjb2hvcnQuPC9xPg0KDQpgYGB7cn0NCmRkc19wdyA8LSBkZHNbLGNvbERhdGEoZGRzKSRvcmlnaW49PSJQVyJdDQpkZHNfcHcgPC0gREVTZXEoZGRzX3B3KQ0KcmVzdWx0cyhkZHNfcHcpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpICU+JSBhcnJhbmdlKHBhZGopDQpkZHNfbmJiIDwtIGRkc1ssY29sRGF0YShkZHMpJG9yaWdpbj09Ik5CQiJdDQpkZHNfbmJiIDwtIERFU2VxKGRkc19uYmIpDQpyZXN1bHRzKGRkc19uYmIpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpICU+JSBhcnJhbmdlKHBhZGopDQpgYGANCg0KSG93IGNhbiB0d28gY29ob3J0cyBleGhpYml0IHN1Y2ggZGlmZmVyZW5jZXMgaW4gdGhlaXIgcmVzdWx0cz8gV2hhdCBkb2VzIHRoaXMNCnN1Z2dlc3QgYWJvdXQgaW50ZXItc3R1ZHkgcmVwbGljYWJpbGl0eT8NCg0KDQoNCiMgR2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNlDQoNCldlIGFyZSBnb2luZyB0byB1c2UgdGhlIGBmZ3NlYWAgcGFja2FnZSB0byBydW4gYSBnZW5lIHNldCBlbnJpY2htZW50IGFuYWx5c2lzDQpvbiB5b3VyIHJlc3VsdHMuDQoNCjxxPkluc3RhbGwgdGhlIGBmZ3NlYWAgcGFja2FnZSB1c2luZyBgQmlvY29uZHVjdG9yOjppbnN0YWxsKGZnc2VhKWAgYW5kIGxvYWQNCnRoZSBwYWNrYWdlLjwvcT4NCg0KYGBge3J9DQpyZXF1aXJlKCdmZ3NlYScpDQpgYGANCg0KV2UgY2FuIGRvd25sb2FkICJnbXQiIGZvcm1hdHRlZCBmaWxlcyBmcm9tDQpbTVNpZ0RCXShodHRwOi8vd3d3LmdzZWEtbXNpZ2RiLm9yZy9nc2VhL21zaWdkYi9jb2xsZWN0aW9ucy5qc3ApIHdlYnNpdGUsIHdoaWNoDQpjYW4gYmUgaW1wb3J0ZWQgaW50byBSIHVzaW5nIHRoZSBgZ210UGF0aHdheXMoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYGZnc2VhYA0KcGFja2FnZS4gSSBoYXZlIGFscmVhZHkgZG93bmxvYWRlZCB0aGUgS0VHRyBkYXRhYmFzZSwgd2hpY2ggY2FuIGJlIGxvYWRlZCBpbnRvDQphIHZhcmlhYmxlIGxpa2Ugc286DQoNCiAgICBLRUdHX3BhdGh3YXlzIDwtIGdtdFBhdGh3YXlzKCJjMi5jcC5rZWdnLnYyMDIyLjEuSHMuc3ltYm9scy5nbXQiKQ0KDQo8cT5Mb2FkIHRoZSBLRUdHIHBhdGh3YXkgbGlzdCB1c2luZyB0aGUgcHJldmlvdXMgY29tbWFuZC48L3E+DQoNCmBgYHtyfQ0KS0VHR19wYXRod2F5cyA8LSBnbXRQYXRod2F5cygiYzIuY3Aua2VnZy52MjAyMi4xLkhzLnN5bWJvbHMuZ210IikNCmBgYA0KDQpXZSBhcmUgZ29pbmcgdG8gcHJlcGFyZSBhbiBpbnB1dCBmb3IgdGhlIGBmZ3NlYSgpYCBmdW5jdGlvbi4gV2UgbmVlZCB0byByYW5rDQp0aGUgZ2VuZXMgYWNjb3JkaW5nIHRvIG91ciB2YWx1ZSBvZiBpbnRlcmVzdCAoaW4gb3VyIGNhc2UsIGl0IHdpbGwgYmUgdGhlDQoic3RhdCIgY29sdW1uIG9mIG91ciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzLCBhbHRob3VnaCB5b3UgY291bGQgdXNlDQotbG9nMTAocC12YWx1ZSkgb3IgbG9nMkZvbGRDaGFuZ2UpLg0KDQo8cT5DcmVhdGUgYSB2ZWN0b3Igd2l0aCB0aGUgInN0YXQiIHZhbHVlcyBmcm9tIHRoZSByZXN1bHRzIGFuZCBuYW1lIHRoZSB2YWx1ZXMNCndpdGggdGhlIGNvcnJlc3BvbmRpbmcgZ2VuZSBuYW1lcy48cT4NCg0KYGBge3J9DQpyYW5rcyA8LSByZXMkc3RhdA0KbmFtZXMocmFua3MpIDwtIHJlcyRnZW5lX25hbWUNCmBgYA0KDQo8cT5SdW4gYGdzZWFfcmVzIDwtIGZnc2VhKEtFR0dfcGF0aHdheXMsIHJhbmtzKWAgYW5kIGV4cGxvcmUgdGhlIHJlc3VsdHMNCigicmFua3MiIGlzIHRoZSB2ZWN0b3Igb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gc3RhdHMpLjwvcT4NCg0KYGBge3J9DQpnc2VhX3JlcyA8LSBmZ3NlYShLRUdHX3BhdGh3YXlzLCByYW5rcykgJT4lIGFycmFuZ2UocGFkaikNCmdzZWFfcmVzDQpgYGANCg0KV2hhdCBpcyBhdCB0aGUgdG9wPyBDYW4geW91IHJ1biB0aGUgR1NFQSB3aXRoIHRoZSByZXN1bHRzIGZyb20gdGhlIHR3bw0KYWx0ZXJuYXRlIG1vZGVscyAoaS5lLiwgd2l0aCBhbmQgd2l0aG91dGggYWNjb3VudGluZyBmb3IgdGhlIGVmZmVjdCBvZiBSSU4pPw0KDQo8cT5DcmVhdGUgYSBuZXcgdmVjdG9yIHdpdGggdGhlIHN0YXRzIG9mIHRoZSBhcHByb3ByaWF0ZSByZXN1bHRzIGFuZCBydW4NCmBmZ3NlYWAgYWdhaW4uPHE+DQoNCmBgYHtyfQ0KcmFua3NfcmluIDwtIHJlc19yaW4kc3RhdA0KbmFtZXMocmFua3NfcmluKSA8LSByZXNfcmluJGdlbmVfbmFtZQ0KZ3NlYV9yZXNfcmluIDwtIGZnc2VhKEtFR0dfcGF0aHdheXMsIHJhbmtzX3JpbikgJT4lIGFycmFuZ2UocGFkaikNCmdzZWFfcmVzX3Jpbg0KYGBgDQoNCldoYXQgaGFwcGVucyBpZiB5b3UgYWRkIE5ldXJvbiBvciBTeW5hcHNlcyB0byB0aGUgZGVzaWduIG1vZGVsPyBIb3cgdGhhdCBkb2VzDQphZmZlY3QgdGhlIEdTRUEgcmVzdWx0cz8NCg0KPHE+UnVuIGFub3RoZXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdXNpbmcgTmV1cm9uIGFuZC9vciBTeW5hcHNlcyBhcw0KY292YXJpYXRlcyBhbmQgZXhwbG9yZSB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIGVucmljaGVkIHBhdGh3YXlzLjxxPg0KDQpgYGB7cn0NCmRkc19uZXUgPC0gZGRzDQpkZXNpZ24oZGRzX25ldSkgPC0gfiBzZXggKyBhZ2VfeWVhcnMgKyByaW4gKyBOZXVyb24gKyBTeW5hcHNlcyArIGNvbmRpdGlvbg0KZGRzX25ldSA8LSBERVNlcShkZHNfbmV1KQ0KcmVzX25ldSA8LSByZXN1bHRzKGRkc19uZXUpICU+JSBhc190aWJibGUocm93bmFtZXM9ImdlbmVfbmFtZSIpDQpyYW5rc19uZXUgPC0gcmVzX25ldSRzdGF0DQpuYW1lcyhyYW5rc19uZXUpIDwtIHJlc19uZXUkZ2VuZV9uYW1lDQpnc2VhX3Jlc19uZXUgPC0gZmdzZWEoS0VHR19wYXRod2F5cywgcmFua3NfbmV1KSAlPiUgYXJyYW5nZShwYWRqKQ0KZ3NlYV9yZXNfbmV1DQpgYGANCg0KDQpDYW4geW91IHRoaW5rIG9mIGEgd2F5IHRvIGlkZW50aWZ5IHRoZSBwYXRod2F5cyB0aGF0IGFyZSBtb3JlIHN1c2NlcHRpYmxlIHRvDQpSSU4gYW5kL29yIE5ldXJvbiBjb3JyZWN0aW9uPw0KDQoNCg==